说一说Java中的JUC

JUC

1.什么是JUC

2.进程和线程

进程 : cpu资源分配的最小单位

线程 : cpu调度和执行的最小单位

并发是指多个任务在同一个时间段内交替执行,通过时间片轮转等方式实现任务间的切换。换句话说,并发是指多个任务能够同时存在,但不一定同时执行。这种情况下,任务之间可能会相互干扰,需要使用同步机制来协调它们的执行次序。

并行则是指多个任务在同一时刻同时执行,每个任务都分配到独立的处理器核心或计算单元上进行并行执行。并行可以大大提高程序的执行速度,特别是对于需要大量计算的任务而言。

简而言之,可以将并发看作是任务的概念,而并行则是执行的方式。并发强调任务之间的交替执行,而并行则强调任务的同时执行。

在实际应用中,可以通过多线程、多进程等方式实现并发和并行。例如,在多核处理器上运行多个线程或进程,每个线程或进程负责执行一个任务,即可实现并行和并发。在分布式系统中,可以将任务分配给多台计算机进行并行处理。

综上所述,并发和并行是两个重要的计算机概念,理解它们的区别有助于优化和提高程序的性能。

并发 :多个任务在同一时刻执行

并行 : 多个任务在同一时间段内执行

并发编程的本质:充分利用CPU的资源

Java中线程有几种状态?

public enum State {
    /**
     * Thread state for a thread which has not yet started.
     */
    NEW,

    /**
     * Thread state for a runnable thread.  A thread in the runnable
     * state is executing in the Java virtual machine but it may
     * be waiting for other resources from the operating system
     * such as processor.
     */
    RUNNABLE,

    /**
     * Thread state for a thread blocked waiting for a monitor lock.
     * A thread in the blocked state is waiting for a monitor lock
     * to enter a synchronized block/method or
     * reenter a synchronized block/method after calling
     * {@link Object#wait() Object.wait}.
     */
    BLOCKED,

    /**
     * Thread state for a waiting thread.
     * A thread is in the waiting state due to calling one of the
     * following methods:
     * 
    *
  • {@link Object#wait() Object.wait} with no timeout
  • *
  • {@link #join() Thread.join} with no timeout
  • *
  • {@link LockSupport#park() LockSupport.park}
  • *
* *

A thread in the waiting state is waiting for another thread to * perform a particular action. * * For example, a thread that has called Object.wait() * on an object is waiting for another thread to call * Object.notify() or Object.notifyAll() on * that object. A thread that has called Thread.join() * is waiting for a specified thread to terminate. */ WAITING, /** * Thread state for a waiting thread with a specified waiting time. * A thread is in the timed waiting state due to calling one of * the following methods with a specified positive waiting time: *

    *
  • {@link #sleep Thread.sleep}
  • *
  • {@link Object#wait(long) Object.wait} with timeout
  • *
  • {@link #join(long) Thread.join} with timeout
  • *
  • {@link LockSupport#parkNanos LockSupport.parkNanos}
  • *
  • {@link LockSupport#parkUntil LockSupport.parkUntil}
  • *
*/
TIMED_WAITING, /** * Thread state for a terminated thread. * The thread has completed execution. */ TERMINATED; }

wait 和 notify 的区别

  1. 所在包不同,分别为 object和thread
  2. wait需要在同步代码块,会自动释放当前锁

3.Lock锁

传统Synchronized

package com.hkd.rjxy;

public class demo02 {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        //多线程操作
        new Thread(() -> {
            for (int i = 0; i < 40; i++) {
                ticket.sale();
            }
        },"A").start();

        new Thread(() -> {
            for (int i = 0; i < 40; i++) {
                ticket.sale();
            }
        },"B").start();

        new Thread(() -> {
            for (int i = 0; i < 40; i++) {
                ticket.sale();
            }
        },"C").start();
    }
}

//资源类
class Ticket{
    //属性,方法
    private int number = 50;

    //卖票的方式
    //synchronized 本质就是排队
    public synchronized void sale() {
        if (number > 0){
            System.out.println(Thread.currentThread().getName() + "卖出了 " + number-- + "票,剩余"+number);

        }
    }
}

Lock

说一说Java中的JUC_第1张图片

说一说Java中的JUC_第2张图片

默认是非公平锁,参数为true是公平锁

package com.hkd.rjxy;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class demo03 {
    public static void main(String[] args) {
        Ticket2 ticket = new Ticket2();
        //多线程操作
        new Thread(() -> {
            for (int i = 0; i < 40; i++) {
                ticket.sale();
            }
        },"A").start();

        new Thread(() -> {
            for (int i = 0; i < 40; i++) {
                ticket.sale();
            }
        },"B").start();

        new Thread(() -> {
            for (int i = 0; i < 40; i++) {
                ticket.sale();
            }
        },"C").start();
    }
}
class Ticket2{
    //属性,方法
    private int number = 50;
    Lock lock = new ReentrantLock();
    //卖票的方式
    //synchronized 本质就是排队
    public void sale() {
        lock.lock(); // 加锁
        try {
            if (number > 0){
                System.out.println(Thread.currentThread().getName() + "卖出了 " + number-- + "票,剩余"+number);
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }

    }
}

Synchronized 和 Lock的区别

  1. synchronized内置的java关键字,Lock是一个接口
  2. synchronized无法判断锁的状态,Lock可以判断是否获取到了锁
  3. synchronized会自动释放锁资源,而Lock锁需要手动unlock() 否则会死锁
  4. synchronized会一直等待锁释放,Lock就不一定会等待下去
  5. synchronized适合少量代码,Lock适合大量

4.生产者和消费者问题

说一说Java中的JUC_第3张图片

wait的调用周围的while循环检查正在等待的条件
同时超过两个线程记得notifyAll,否则会死锁!

JUC版生产者和消费者

public class B {
    public static void main(String[] args) {
        Data2 data = new Data2();

        new Thread(() -> {
            for (int i = 0; i < 100; i++){
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },"A").start();

        new Thread(() -> {
            for (int i = 0; i < 100; i++){
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },"B").start();

        new Thread(() -> {
            for (int i = 0; i < 100; i++){
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },"C").start();

        new Thread(() -> {
            for (int i = 0; i < 100; i++){
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },"D").start();
    }

}

class Data2{
    //数字 资源类
    private int number = 0;
    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();
    public void increment() throws InterruptedException {
        lock.lock();
        try {
            while (number != 0){
                //等待
                condition.await();
            }
            number++;
            System.out.println(Thread.currentThread().getName() +  " : " + number);
            condition.signalAll();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void decrement() throws InterruptedException {
        lock.lock();
        try {
            while (number == 0){
                //等待
                condition.await();
            }
            number--;
            System.out.println(Thread.currentThread().getName() + " : "+number);
            condition.signalAll();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }

    }
}

为什么有synchronized了还要有Lock接口

Condition的优势 : 精准的通知和唤醒线程!!!
指定线程打印数字
package com.hkd.rjxy.pc;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class C {
    public static void main(String[] args) {
        Data3 data = new Data3();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                data.printA();
            }
        },"A").start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                data.printB();
            }
        },"B").start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                data.printC();
            }
        },"C").start();
    }
}

class Data3{ //资源类
    private Lock lock = new ReentrantLock();
    Condition condition1 = lock.newCondition();
    Condition condition2 = lock.newCondition();
    Condition condition3 = lock.newCondition();
    private int number = 1;
    //我们要完成的任务 : 1A 2B 3C

    public void printA(){
        lock.lock();
        try {
            //写业务代码
            while (number != 1){
                condition1.await();
            }
            System.out.println(Thread.currentThread().getName() + " : " + number++);
            condition2.signal();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    public void printB(){
        lock.lock();
        try {
            //写业务代码
            while (number != 2){
                condition2.await();
            }
            System.out.println(Thread.currentThread().getName() + " : " + number++);
            condition3.signal();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    public void printC(){
        lock.lock();
        try {
            //写业务代码
            while (number != 3){
                condition3.await();
            }
            System.out.println(Thread.currentThread().getName() + " : " + number);
            number = 1;
            condition1.signal();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}

说一说Java中的JUC_第4张图片

condition一个重要作用就是生产线

下单 -> 支付 -> 交易 -> 物流…

5. 8锁现象

public class Test1 {
    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread(phone::sendMessage).start();
        new Thread(phone::call).start();
    }
}
class Phone{
    public synchronized void sendMessage(){
        System.out.println("message");
    }

    public synchronized void call() {
        System.out.println("call");
    }
}

说一说Java中的JUC_第5张图片

这个例子永远是message在先,这是因为synchronized关键字修饰方法时,锁的对象是方法的调用者 -> phone

两个方法用的是同一个对象的锁,谁先拿到谁先执行

import java.util.concurrent.TimeUnit;

public class Test1 {
    public static void main(String[] args) throws InterruptedException {
        Phone phone = new Phone();
        new Thread(phone::sendMessage).start();

        TimeUnit.SECONDS.sleep(1);

        new Thread(phone::hello).start();
    }
}
class Phone{
    public synchronized void sendMessage(){
        try {
            TimeUnit.SECONDS.sleep(4);//延迟时间很长
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("message");
    }

    public synchronized void call() {
        System.out.println("call");
    }
    
    //这里没有锁,肯定hello先执行
    public void hello() {
        System.out.println("hello");
    }
}

如果方法前面有static修饰,那么锁的是class对象 Phone.class
    

6. 集合类

多线程下不安全集合类报错 : Java.util.ConcurrentModificationException 并发修改异常

并发环境下ArrayList是不安全的,怎么解决?

List list = new Vector();

List<String> list = Collections.synchronizedList(new ArrayList<>());

List<String> list = new CopyOnWriteArrayList<>();//写入时复制 多个线程调用的 

学习CopyOnWriteArrayList

源码描述:

ArrayList 的线程安全变体,其中所有可变操作(addset 等)都是通过制作底层数组的新副本来实现的。

这通常成本太高,但当遍历操作的数量远远超过突变时,它可能more 比替代方法更有效,并且在您不能或不想同步遍历但需要排除并发线程之间的干扰时很有用。 “快照”样式的迭代器方法在创建迭代器时使用对数组状态的引用。这个数组在迭代器的生命周期内永远不会改变,所以干扰是不可能的,迭代器保证不会抛出 ConcurrentModificationException 。迭代器不会反映自迭代器创建以来对列表的添加、删除或更改。不支持对迭代器本身(removesetadd)进行元素更改操作。这些方法抛出 UnsupportedOperationException

允许所有元素,包括 null

内存一致性影响:与其他并发集合一样,在将对象放入 CopyOnWriteArrayList 发生在之前 之前线程中的操作是在另一个线程中从 CopyOnWriteArrayList 访问或删除该元素之后的操作。

此类是 Java 集合框架 的成员。

总结,写操作上锁,先复制一份副本,写好后插入,并将容器指向新的容器,释放锁,用的Lock锁,没用synchronized,效率高

集合Set同理,HashSet的底层是什么?

public HashSet() {
        map = new HashMap<>();
    }
//add的本质就是map.put,所以key不能重复
public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }

ConcurrentHashMap

7.Callable

说一说Java中的JUC_第6张图片

package com.hkd.rjxy.unsafe;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class MyCallable{
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyCallableDemo callable = new MyCallableDemo();
        FutureTask<Integer> futureTask = new FutureTask<>(callable);
        new Thread(futureTask,"A").start();
    }
}
class MyCallableDemo implements Callable<Integer>{
    @Override
    public Integer call() {
        System.out.println("call");
        return 11;
    }
}
s1mple

8.高频辅助类

8.1CountDownLatch 减法计数器

一种同步辅助工具,允许一个或多个线程等待,直到其他线程中执行的一组操作完成。

CountDownLatch 使用给定的 count 进行初始化。 await 方法会阻塞,直到当前计数由于调用 countDown() 方法而变为零,之后所有等待的线程都会被释放,并且任何后续的 await 调用都会立即返回。这是一种一次性现象——无法重置计数。如果您需要重置计数的版本,请考虑使用 CyclicBarrier

CountDownLatch 是一种多功能同步工具,可用于多种用途。初始化为 1 的 CountDownLatch 用作简单的开/关锁存器或门:所有调用 await 的线程都在门处等待,直到它被调用 countDown() 的线程打开。初始化为 NCountDownLatch 可用于使一个线程等待,直到 N 个线程完成某个操作,或者某个操作已完成 N 次。

CountDownLatch 的一个有用属性是它不需要调用 countDown 的线程在继续之前等待计数达到零,它只是阻止任何线程继续通过 await 直到所有线程都可以通过。

import java.util.concurrent.CountDownLatch;

public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(6);//总数为6
        for (int i = 0; i < 6; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + " GO");
                countDownLatch.countDown();//计数器减一
            },String.valueOf(i)).start();
        }
        countDownLatch.await();//等待计数器归零
        System.out.println("it is over");
    }
}
0 GO
5 GO
3 GO
4 GO
1 GO
2 GO
it is over
减法计数器

8.2CyclicBarrier 加法计数器

允许一组线程全部等待彼此到达公共屏障点的同步辅助工具。 CyclicBarriers 在涉及必须偶尔相互等待的固定大小线程组的程序中很有用。屏障被称为cyclic,因为它可以在等待线程被释放后重新使用。

CyclicBarrier 支持可选的 Runnable 命令,该命令在每个障碍点运行一次,在派对中的最后一个线程到达之后,但在释放任何线程之前。这个 barrier action 对于在任何一方继续之前更新共享状态很有用。

CyclicBarrier cyclicBarrier = new CyclicBarrier(8,() -> {
            System.out.println("计数器到达8");
        });
        for (int i = 0; i < 7; i++) {
             final int temp = i;
             new Thread(() -> {
                 System.out.println(Thread.currentThread().getName() + "收集" +
                         temp + "个");
                 try {
                     cyclicBarrier.await();//等待和计数
                 } catch (InterruptedException e) {
                     throw new RuntimeException(e);
                 } catch (BrokenBarrierException e) {
                     throw new RuntimeException(e);
                 }
             }).start();
        }
//如果计数器到达不了传入的参数8,程序就会死在这
Thread-0收集0Thread-5收集5Thread-3收集3Thread-1收集1Thread-4收集4Thread-2收集2Thread-6收集6//如果传入参数调整为7,程序就会输出
Thread-3收集3Thread-5收集5Thread-0收集0Thread-6收集6Thread-1收集1Thread-2收集2Thread-4收集4个
计数器到达7

8.3Semaphore

计数信号量。从概念上讲,信号量维护一组许可。如果有必要,每个 acquire() 都会阻塞,直到获得许可,然后再获取许可。每个 release() 添加一个许可,可能会释放一个阻塞的获取者。但是,没有使用实际的许可对象; Semaphore 只是保持可用数量的计数并相应地采取行动。

信号量通常用于限制可以访问某些(物理或逻辑)资源的线程数

Semaphore semaphore = new Semaphore(3);
        for (int i = 1; i <= 6; i++) {
            new Thread(()->{
                try {
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName()+"拿到资源");
                    TimeUnit.SECONDS.sleep(2);
                    System.out.println(Thread.currentThread().getName()+"释放资源");
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }finally{
                    semaphore.release();
                }
            },String.valueOf(i)).start();
        }
2拿到资源
1拿到资源
3拿到资源
3释放资源
1释放资源
2释放资源
4拿到资源
6拿到资源
5拿到资源
5释放资源
6释放资源
4释放资源

多个共享资源的互斥使用,并发限流

9.读写锁

public interface ReadWriteLock

ReadWriteLock 维护一对关联的 locks ,一个用于只读操作,一个用于写入。 读锁 可以由多个读取器线程同时持有,只要没有写入器。 写锁 是排他性的。

所有 ReadWriteLock 实现必须保证 writeLock 操作的内存同步效果(如 Lock 接口中所指定)也适用于关联的 readLock 。也就是说,成功获取读锁的线程将看到在先前释放写锁时所做的所有更新。

与互斥锁相比,读写锁在访问共享数据时允许更高级别的并发。它利用了这样一个事实,即虽然一次只有一个线程(writer 线程)可以修改共享数据,但在许多情况下,任何数量的线程都可以并发读取数据(因此有 reader 线程)。从理论上讲,使用读写锁所允许的并发性增加会导致使用互斥锁的性能提升。实际上,这种并发性的增加只会在多处理器上完全实现,并且只有在共享数据的访问模式合适的情况下才能实现。

读写锁是否会比使用互斥锁提高性能取决于与修改数据相比读取数据的频率、读写操作的持续时间以及对数据的争用——即即同时尝试读取或写入数据的线程数。例如,一个集合最初填充了数据,此后很少被修改,同时被频繁搜索(例如某种目录)是使用读写锁的理想候选者。但是,如果更新变得频繁,那么数据大部分时间都被独占锁定,并且并发性几乎没有增加。此外,如果读取操作太短,读写锁实现的开销(本质上比互斥锁更复杂)会主导执行成本,特别是因为许多读写锁实现仍然通过一个序列化所有线程一小段代码。最终,只有分析和测量才能确定读写锁的使用是否适合您的应用程序。

尽管读写锁的基本操作很简单,但实现必须做出许多策略决策,这可能会影响给定应用程序中读写锁的有效性。这些策略的示例包括:

  • 决定授予读锁还是授予写锁,当读写者都在等待时,在写者释放写锁的时刻。作者偏好很常见,因为写作预计会很短且不频繁。读者偏好不太常见,因为如果读者像预期的那样频繁和长寿,它会导致写入的长时间延迟。公平或“按顺序”实现也是可能的。
  • 确定在读者处于活动状态且写入者正在等待时请求读锁的读者是否被授予读锁。对读者的偏好可以无限期地延迟作者,而对作者的偏好可以降低并发的可能性。
  • 判断锁是否可重入:拥有写锁的线程能否重新获取?是否可以在持有写锁的同时获取读锁?读锁本身是可重入的吗?
  • 写锁能否降级为读锁而不允许干预写入器?读锁能否升级为写锁,优先于其他等待的读者或写者?

在评估给定实现对您的应用程序的适用性时,您应该考虑所有这些事情。

写线程互斥,读线程不互斥

说一说Java中的JUC_第7张图片

10.阻塞队列BlockingQueue

添加操作:

add()会抛出异常

offer()不会抛出异常,返回false

弹出操作:

remove()会抛出异常

poll()不会抛出异常,返回null

查看队首:

element()抛出异常

peek()不会抛出异常,返回null

等待阻塞操作:

put()队列没位置了一直阻塞

take()队列空了一直阻塞

blockingQueue.offer("d",2, TimeUnit.SECONDS);//阻塞的话,等待2s,超时退出

同步队列SynchronousQueue

SynchronousQueue(同步队列)是Java并发包中的一种特殊的队列实现。它是一个没有存储元素的阻塞队列,主要用于线程之间的数据交换。

在SynchronousQueue中,每个插入操作必须等待另一个线程的相应删除操作,反之亦然。换句话说,当一个线程尝试向SynchronousQueue中插入元素时,它将被阻塞,直到另一个线程从队列中取走这个元素;同样,当一个线程尝试取出元素时,它也会被阻塞,直到另一个线程向队列中插入一个元素。

由于这种特性,SynchronousQueue被用作线程之间的一种双向数据交换的工具。它可以用于实现生产者-消费者模式,让生产者线程和消费者线程能够同步地进行数据交换,而且不需要使用显式的锁或条件变量。

总的来说,SynchronousQueue是一种非常特殊且有用的并发工具,可以帮助开发者实现高效的线程之间通信和协作。
    BlockingQueue<String> synchronousQueue = new SynchronousQueue(); // 同步队列
        new Thread(()->{
            try {
                System.out.println(Thread.currentThread().getName() + "put 1");
                synchronousQueue.put("1");
                System.out.println(Thread.currentThread().getName() + "put 2");
                synchronousQueue.put("2");
                System.out.println(Thread.currentThread().getName() + "put 3");
                synchronousQueue.put("3");
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        },"T1").start();

        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName() + synchronousQueue.take());
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName() + synchronousQueue.take());
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName() + synchronousQueue.take());
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        },"T2").start();

T1put 1
T21
T1put 2
T22
T1put 3
T23

11.线程池(重点)

Java中的线程池是一种用于管理和重复利用线程的机制,它可以有效地控制并发执行的线程数量,从而提高程序的性能和响应速度。

线程池由一个线程队列和一个任务队列组成。当一个任务到来时,线程池会从线程队列中取出一个空闲的线程来执行任务,如果没有空闲线程,任务将被放入任务队列中等待。线程池会根据设定的参数来动态调整线程数,以适应当前的工作负载,避免创建过多的线程消耗过多的系统资源。

通过使用线程池,可以减少线程创建和销毁的开销,提高系统的稳定性和性能。此外,线程池还提供了一些额外的功能,比如定时执行任务、对执行任务进行统计和监控等,使得多线程编程变得更加方便和灵活。

在Java中,线程池通过Executor框架提供支持,常用的线程池实现类包括ThreadPoolExecutorScheduledThreadPoolExecutor,开发者可以根据自己的需求选择合适的线程池类型和参数配置来优化程序的性能。

线程池在多线程编程中具有许多好处,主要包括以下几点:

  1. 降低资源消耗:线程池可以重复利用已创建的线程,避免频繁创建和销毁线程所带来的性能开销,从而减少了系统资源的消耗。
  2. 提高响应速度:线程池能够快速分配线程来处理任务,无需等待新线程的创建,因此可以更快地响应用户请求,提高了系统的响应速度。
  3. 控制并发度:通过限制线程池中的线程数量,可以有效控制并发执行的任务数量,避免因线程过多而导致系统负载过重,提高了系统的稳定性。
  4. 灵活管理:线程池提供了丰富的参数配置和管理功能,可以动态调整线程数、统计执行情况、监控线程状态等,使得线程的管理变得更加灵活和方便。
  5. 提供任务队列:线程池通常包含一个任务队列,可以暂存未执行的任务,当线程池中的线程都在忙碌时,新的任务可以排队等待执行,避免任务丢失或被拒绝执行。
  6. 统一管理:通过线程池,可以统一管理和监控系统中的线程,使得多线程编程变得更加规范和可控,降低了出错的可能性。

综上所述,线程池能够有效地提高系统的性能、稳定性和可维护性,是多线程编程中不可或缺的重要工具。

//三个方法
ExecutorService threadPool = Executors.newSingleThreadExecutor();// 单个线程
        ExecutorService executorService = Executors.newFixedThreadPool(5);// 创建一个固定的线程池的大小
        ExecutorService executorService1 = Executors.newCachedThreadPool();// 可伸缩的
//本质 new ThreadPoolExecutor()

//7个参数
public ThreadPoolExecutor(int corePoolSize,//核心线程数
                              int maximumPoolSize,//最大线程数
                              long keepAliveTime,//存活时间
                              TimeUnit unit,//时间单位
                              BlockingQueue<Runnable> workQueue,//阻塞队列
                              ThreadFactory threadFactory,//线程工场,创造线程的,一般不用动
                              RejectedExecutionHandler handler//拒绝策略)

问题,最大线程数如何设置好

cpu密集型和IO密集型

几核CPU定义为几,可以保证CPU效率高

判断程序中有多少个线程十分消耗IO,设为两倍

12.四大函数式接口(imp)

//函数型接口,有一个输入参数,有一个输出
        Function function = new Function<String,String>() {
            @Override
            public String apply(String o) {
                return o;
            }
        };

        System.out.println(function.apply("asd"));

        Function<String,String> f = (str) -> {return str;};
        System.out.println(f.apply("aa"));
//断定型接口,有一个输入参数,返回值只能是布尔值
        Predicate<String> predicate = new Predicate<String>() {
            @Override
            public boolean test(String s) {
                return s.isEmpty();
            }
        };

        System.out.println(predicate.test(""));
//消费型接口,只有输入,没有返回值
        Consumer<String> consumer = new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s);
            }
        };
        consumer.accept("asd");
//供给型接口
        Supplier<String> stringSupplier = new Supplier<String>() {
            @Override
            public String get() {
                return "asd";
            }
        };

        Supplier<Integer> integerSupplier = () -> {return 1;};

        System.out.println(stringSupplier.get());
        System.out.println(integerSupplier.get());

13.stream流式计算

User u1 = new User(1,"a",21);
        User u2 = new User(2,"b",22);
        User u3 = new User(3,"c",23);
        User u4 = new User(4,"d",24);
        User u5 = new User(5,"e",25);
        User u6 = new User(6,"f",26);
        List<User> list = Arrays.asList(u1, u2, u3, u4, u5,u6);
        //id偶数,年龄大于23,用户名转为大写,字母倒着排序,只输出一个用户

        //先打印id为偶数
        list.stream().filter(user->{return user.getId() %2 == 0;}).filter(user->{return user.getAge() > 23;})
                .map(user -> {return user.getName().toUpperCase();})
                .sorted((uu1,uu2) -> {return uu2.compareTo(uu1);})
                .limit(2)
                .forEach(System.out::println);

14.ForkJoin

15.Future

16.JMM

Java内存模型,不存在,是一个约定

  1. 线程解锁前,必须把共享变量立刻及时刷回主存
  2. 线程加锁前,必须读取主存中的最新值到工作内存中
  3. 加锁和解锁必须是同一把锁

线程B修改了flag=false,线程A不能及时拿到最新值

内存交互操作有8种:lock unlock read load use assign store write

请你谈谈对volatile的理解

17.volatile

volatile是关键字,可以理解为轻量级的同步机制

  1. 可见性
  2. 不保证原子性
  3. 禁止指令重排序
private static int num = 0;
    public static void main(String[] args) {
        //main
        


        new Thread(()->{ // 线程1
            while(num == 0){
                //一直循环
            }
        }).start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        num = 1;
        System.out.println(num);
    }
// num调整为1,线程1应该停止才对,但没有停止

说一说Java中的JUC_第8张图片

private volatile static int num = 0; // 加入关键字volatile就会停止,保证可见性验证
    public static void main(String[] args) {
        //main

        new Thread(()->{ // 线程1
            while(num == 0){
                //一直循环
            }
        }).start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        num = 1;
        System.out.println(num);
    }

说一说Java中的JUC_第9张图片

验证volatile不保证原子性(不可分割:

线程A在执行任务的时候,是不能被打扰分割的

// 验证volatile不保证原子性
    private static int num = 0;

    public static void add() {
        num++;
    }

    public static void main(String[] args) {
        // 理论上num应为20000
        for (int i = 0; i < 20; i++) {
            new Thread(()->{
                for (int j = 0; j < 1000; j++) {
                    add();
                }
            }).start();
        }

        while (Thread.activeCount() > 2){
            //main gc
            Thread.yield();
        }

        //循环结束就跑完了
        System.out.println(num);
    }

说一说Java中的JUC_第10张图片

不加Lock和synchronized怎么保证原子性

num++不是原子性操作

可以使用原子类

private volatile static AtomicInteger num = new AtomicInteger();// 原子类

    public static void add() {
        num.getAndIncrement();// + 1方法,CAS
    }

    public static void main(String[] args) {
        // 理论上num应为20000
        for (int i = 0; i < 20; i++) {
            new Thread(()->{
                for (int j = 0; j < 1000; j++) {
                    add();
                }
            }).start();
        }

        while (Thread.activeCount() > 2){
            //main gc
            Thread.yield();
        }

        //循环结束就跑完了
        System.out.println(num);
    }

说一说Java中的JUC_第11张图片

没加锁得到了正确结果

Atmoic类底层直接和操作系统挂钩!在内存中修改值!Unsafe类是一个很特殊的存在

什么是指令重排序: 你写的程序,计算机并不是按照你写的那样去执行的

什么情况发生重排序 : 原代码 -> 编译器优化重排 -> 指令并行也可能重排 -> 内存系统也会重排 -> 执行

18.四个单例模式实现

19.深入理解CAS

  1. 什么是CAS?它有什么作用?
  • 回答: CAS是一种用于实现多线程同步的技术,通过比较内存位置的值与预期数值,并在相等时将该位置的值更新为新值,整个过程不用加锁,避免了锁机制带来的性能损耗。CAS通常用来实现无锁算法,并提高并发性能
  1. Java中如何使用CAS?
  • 回答:在Java中可以通过java.util.concurrent.atomic包提供的原子类来使用,比如AtomicInteger,AtomicLong等。这些原子类提供了CAS操作的接口,可以在多线程环境下对共享变量进行原子操作

    3.CAS操作有那些问题?

  • 回答:ABA问题,假设内存中的值原本是A,线程1读取到并进行操作,线程2修改为B,并在某一时刻线程1又将其改回A。此时线程1在进行CAS操作时,由于内存中确实是A,所以CAS操作成功,但实际 上这个位置的值已经发生了变化。此外CAS需要循环测试的,当竞争激烈时,会导致性能开销增加

  1. CAS与传统锁机制相比有什么优势?
  • 回答:CAS与传统锁机制相比开销更小,因为不需要线程阻塞。而且在对共享变量进行简单原子操作时,CAS表现得性能更好
  1. CAS的适用场景?
  • 回答资源竞争较少的情况:在资源竞争较少(线程冲突较轻)的情况下,使用synchronized同步锁进行线程阻塞和唤醒切换以及用户态到内核态间的切换操作会额外浪费消耗cpu资源。而CAS基于操作借助C来调用CPU底层指令实现的,不需要进入内核,不需要切换线程,操作自旋几率较少,因此可以获得更高的性能。

20.ABA

在Java开发中,ABA问题通常与并发编程和CAS(Compare and Swap)操作相关。ABA问题指的是一个共享变量的值在操作过程中经历了从A到B再到A的变化,可能导致意外结果的发生。这个问题在多线程环境下尤为常见。以下是一个关于ABA问题的面试题:

题目

请解释什么是ABA问题,在Java并发编程中如何解决这个问题?

答案
  1. ABA问题:ABA问题是指一个共享变量的值在操作过程中经历了从A到B再到A的变化。这种情况在并发编程中会导致意外的结果,尤其是在使用CAS(Compare and Swap)操作时更容易引发ABA问题。
  2. 解决方法:在Java中,可以通过引入版本号或者使用AtomicStampedReference类来解决ABA问题。具体而言,AtomicStampedReference类可以通过引入版本号来检测在CAS操作过程中是否发生了值的变化,从而避免了ABA问题的发生。

示例代码如下:

javaCopy Codeimport java.util.concurrent.atomic.AtomicStampedReference;

public class ABADemo {
    private static AtomicStampedReference<Integer> atomicStampedRef = new AtomicStampedReference<>(1, 0);

    public static void main(String[] args) {
        new Thread(() -> {
            int stamp = atomicStampedRef.getStamp();
            System.out.println("Thread-1: stamp = " + stamp); // 初始版本号

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            atomicStampedRef.compareAndSet(1, 2, stamp, stamp + 1); // A->B
            System.out.println("Thread-1: A->B");
        }).start();

        new Thread(() -> {
            int stamp = atomicStampedRef.getStamp();
            System.out.println("Thread-2: stamp = " + stamp); // 初始版本号

            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            boolean c3 = atomicStampedRef.compareAndSet(2, 1, stamp, stamp + 1); // B->A
            System.out.println("Thread-2: B->A: " + c3);
        }).start();
    }
}

这样,通过AtomicStampedReference类的帮助,我们可以避免ABA问题的发生,确保在CAS操作中不会出现意外的结果。

这样的回答能够展现出对于ABA问题的理解和针对该问题的解决方案在Java中的实际应用能力。

​ 2023/11/12

你可能感兴趣的:(java)