【java多线程】JUC知识总结

人这一生该走的坎儿是一个都落不下,经历多了路也就走顺了,前提是得总结不足加努力向前。 ——辉子

1. 概念

什么是JUC?

JUC就是java.util .concurrent工具包的简称。这是一个处理线程的工具包,JDK 1.5开始出现的。

在这里插入图片描述

2.并行和并发的理解

比如有两个线程,用一个cpu同时运行两个线程,两个线程交错执行,这就是并发。

如果用两个cpu,一个cpu运行一个线程,这就叫并行。

3.用户线程、守护线程

用户线程:自定义线程

守护线程:垃圾回收线程

用户线程结束了,那么守护线程那就结束了。

4.Lock与synchronized不同

  • Lock是一个接口,而synchronized是java中的关键字
  • synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unlock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally快中释放锁。

5.线程间通信

(synchronized实现)

多线程代码编写步骤:

  1. 创建资源类,在资源类创建属性和操作方法
  2. 在资源类操作方法:判断、干活、通知
  3. 创建多个线程,调用资源类的操作方法

案例:有两个线程,实现对一个初试值为0的变量,一个线程对值+1,另一个线程对值-1

输出结果:1 0 1 0 1 0 …

//1.创建资源类,在资源类创建属性和操作方法
class Number{
    private int number = 0;

    //写一个加的方法
    public synchronized void add(){
        //2.在资源类操作方法:判断、干活、通知
        //判断
        if(number !=0){
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //干活
        System.out.println(Thread.currentThread().getName()+"::"+ ++number);
        //通知
        notifyAll();
    }
    //写一个减的方法
    public synchronized void subtract(){
        //判断
        if(number ==0){
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //干活
        System.out.println(Thread.currentThread().getName()+"::"+ --number);
        //通知
        notifyAll();
    }
}
public class ThreadDemo01 {
    //创建多个线程,调用资源类的操作方法
    public static void main(String[] args) {
        Number number = new Number();
        for(int i=0;i<10;i++) {
            new Thread(() -> {
                number.add();
            }, "AA").start();

            new Thread(() -> {
                number.subtract();
            }, "BB").start();
        }
    }
}

虚假唤醒

对于上面的案例代码,在main方法中多加入两个线程

public class ThreadDemo01 {
    //创建多个线程,调用资源类的操作方法
    public static void main(String[] args) {
        Number number = new Number();
        for(int i=0;i<10;i++) {
            new Thread(() -> {
                number.add();
            }, "AA").start();

            new Thread(() -> {
                number.subtract();
            }, "BB").start();

            new Thread(() -> {
                number.add();
            }, "CC").start();

            new Thread(() -> {
                number.subtract();
            }, "DD").start();
        }
    }
}

此时就会出现就会出现-1或者2的情况。

在这里插入图片描述

wait()方法从哪里睡,那么从哪里起。if和while的区别就体现出来了,如果是if的话线程被唤醒就会直接从wait方法往下执行,但是这时候number可能被修改这种情况应该是让线程继续挂起,但是用if会唤醒线程,产生了所谓的虚假唤醒,而如果用while的话,线程被唤醒之后还会在执行一次条件判断。

这就是所谓的虚假唤醒。

这里解决方法是将代码if换成while()

//判断
while(number ==0){
    try {
        wait();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

(Lock实现)

class Share{
    private int number = 0;
    private final ReentrantLock lock = new ReentrantLock();
    Condition condition = lock.newCondition();
    //写一个加的方法
    public void add(){
        //2.在资源类操作方法:判断、干活、通知
        //判断
        lock.lock();
        try{
            while(number !=0){
                try {
                    condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //干活
            System.out.println(Thread.currentThread().getName()+"::"+ ++number);
            //通知
            condition.signalAll();
        }finally {
            lock.unlock();
        }
    }
    //写一个减的方法
    public void sub(){
        //2.在资源类操作方法:判断、干活、通知
        //判断
        lock.lock();
        try{
            while(number ==0){
                try {
                    condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //干活
            System.out.println(Thread.currentThread().getName()+"::"+ --number);
            //通知
            condition.signalAll();
        }finally {
            lock.unlock();
        }
    }
}
public class ThreadDemo02 {
    public static void main(String[] args) {
        Share number = new Share();
        for(int i=0;i<10;i++) {
            new Thread(() -> {
                number.add();
            }, "AA").start();

            new Thread(() -> {
                number.sub();
            }, "BB").start();

            new Thread(() -> {
                number.add();
            }, "CC").start();

            new Thread(() -> {
                number.sub();
            }, "DD").start();
        }
    }
}

6.线程间的定制化通信

案例:

启动三个线程,AA打印5次,BB打印10次,CC打印15次,循环10轮。

//创建资源类
class ShareResource{
    //定义标志位
    private int flag = 1;
    //创建lock锁
    private Lock lock = new ReentrantLock();

    //创建三个condition
    private Condition c1 = lock.newCondition();
//    private Condition c2 = lock.newCondition();
//    private Condition c3 = lock.newCondition();

    //打印5次,参数第几轮
    public void print5(int loop){
        lock.lock();
        try {
            //判断
            while(flag != 1){
                try {
                    c1.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //干活
                for (int i = 1; i <= 5; i++) {
                    System.out.println(Thread.currentThread().getName()+"::"+i+"轮数"+loop);
                }
            }
            //通知
            flag =2;
            c1.signalAll();
        }finally {
           lock.unlock();
        }
    }

    //打印10次,参数第几轮
    public void print10(int loop){
        lock.lock();
        try {
            //判断
            while(flag != 2){
                try {
                    c1.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //干活
                for (int i = 1; i <= 10; i++) {
                    System.out.println(Thread.currentThread().getName()+"::"+i+"轮数"+loop);
                }
            }
            //通知
            flag =3;
            c1.signalAll();
        }finally {
            lock.unlock();
        }
    }
    
    //打印15次,参数第几轮
    public void print15(int loop){
        lock.lock();
        try {
            //判断
            while(flag != 3){
                try {
                    c1.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //干活
                for (int i = 1; i <= 15; i++) {
                    System.out.println(Thread.currentThread().getName()+"::"+i+"轮数"+loop);
                }
            }
            //通知
            flag =1;
            c1.signalAll();
        }finally {
            lock.unlock();
        }
    }
}
public class ThreadDemo03 {
    public static void main(String[] args) {
        ShareResource sr = new ShareResource();

            new Thread(() -> {
                for(int i=1;i<=10;i++) {
                    sr.print5(i);
                }
            }, "AA").start();

            new Thread(() -> {
                for(int i=1;i<=10;i++) {
                    sr.print10(i);
                }

            }, "BB").start();

            new Thread(() -> {
                for(int i=1;i<=10;i++) {
                    sr.print15(i);
                }
            }, "CC").start();

        }
   }

7. 集合线程安全

7.1 ArrayList线程不安全案例演示:

//list集合线程不安全
public class ThreadDemo01 {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();

        for(int i =0;i<30;i++){
            new Thread(() ->{
                list.add(UUID.randomUUID().toString().substring(0,8));
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }
}

多个线程去操作ArrayList的时候,可能会存在集合取的时候内容没放进去,导致并发修改异常ConcurrentModificationException

7.2 解决方法

解决方法一:Vector

public class ThreadDemo01 {
    public static void main(String[] args) {
        List<String> list = new Vector<>();

        for(int i =0;i<30;i++){
            new Thread(() ->{
                list.add(UUID.randomUUID().toString().substring(0,8));
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }
}

Vector是线程安全的,因为其方法上都加了synchronized关键字。

解决方法二:Collections工具类中synchronizedList(List list)方法

public class ThreadDemo01 {
    public static void main(String[] args) {
        //List<String> list = new Vector<>();
        List<String> list = Collections.synchronizedList(new ArrayList<String>());
        for(int i =0;i<30;i++){
            new Thread(() ->{
                list.add(UUID.randomUUID().toString().substring(0,8));
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }
}

解决方法三:CopyOnWriteArrayList类 -->写时复制技术

public class ThreadDemo01 {
    public static void main(String[] args) {
        //List<String> list = new Vector<>();
        //List<String> list = Collections.synchronizedList(new ArrayList<String>());
        CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
        for(int i =0;i<30;i++){
            new Thread(() ->{
                list.add(UUID.randomUUID().toString().substring(0,8));
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }
}

通俗来讲,写是复制技术可以并发的读,写的时候独立写,写的时候,首先复制一份跟之前数组相同大小的数组,然后写入新的内容,最后将新的数组与之前的数组进行合并。

8.多线程锁

8.1 用案例说明锁的8种情况

class Phone {
    public static synchronized void sendMS() {
        try {
            //停留4s
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("---------sendMS");
    }

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

    public void getHello(){
        System.out.println("-------getHello");
    }
}

public class ThreadDemo02 {
    public static void main(String[] args) {
        Phone phone = new Phone();
        Phone phone2 = new Phone();

        new Thread(() -> {
            phone.sendMS();

        },"AA").start();

        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(() -> {
            //phone.sendEmail();
            //phone.getHello();
            phone2.sendEmail();

        },"BB").start();

    }
}

/**
 锁的8种情况
 1.标准访问,先打印短信还是邮件
 ---------sendMS
 ---------sendEmail
 2.停4秒在短信方法内,先打印短信还是邮件
 ---------sendMS
 ---------sendEmail
 3.新增普通的hello方法,先打印短信还是hello
 -------getHello
 ---------sendMS
 4.现在有两部手机,先打印短信还是邮件
 ---------sendEmail
 ---------sendMS
 5.两个静态同步方法,1部手机,先打印短信还是邮件
 ---------sendMS
 ---------sendEmail
 6.两个静态同步方法,2部手机,先打印短信还是邮件
 ---------sendMS
 ---------sendEmail

 7.1个静态同步方法,1个普通同步方法, 1部手机,先打印短信还是邮件
 ---------sendEmail
 ---------sendMS
 8.1个静态同步方法,1个普通同步方法, 2部手机,先打印短信还是邮件
 ---------sendEmail
 ---------sendMS
 */

解释:

  • 1和2 中同步方法synchronized 锁的是当前的对象
  • 3 是与锁无关,所以先执行 getHello
  • 4是有两个对象,用的是两把锁,一个对象一把锁,由于sendMS()等了4秒,所以先输出sendEmail
  • 5是加了static,锁对象就不是this了,那就是class对象了,这相当于共用一把锁了,所以先sendMS,后sendEmail
  • 6由于加了static,虽然创建了两个对象,但是锁的是当前类的class对象,也是共用一把锁。
  • 7和8 是一个静态同步方法,一个普通同步方法,锁对象一个是当前类的class对象,一个是new 的对象,不共用一把锁,所以先执行sendEmail

总结:

java中每一个对象都可以作为锁,具体表现为三个形式。

  1. 对于普通同步方法,锁就是当前实例对象
  2. 对于静态同步方法,锁就是当前类的class对象
  3. 对于同步方法块,锁就是synchronized括号里配置的对象

8.2 公平锁和非公平锁

公平锁:多个线程按照申请锁的顺序去获得锁,线程会直接进入队列去排队,永远都是队列的第一位才能得到锁。

  • 优点:所有的线程都能得到资源,不会饿死在队列中。
  • 缺点:吞吐量会下降很多,队列里面除了第一个线程,其他的线程都会阻塞,cpu唤醒阻塞线程的开销会很大。

非公平锁:多个线程去获取锁的时候,会直接去尝试获取,获取不到,再去进入等待队列,如果能获取到,就直接获取到锁。

  • 优点:可以减少CPU唤醒线程的开销,整体的吞吐效率会高点,CPU也不必取唤醒所有线程,会减少唤起线程的数量。
  • 缺点:你们可能也发现了,这样可能导致队列中间的线程一直获取不到锁或者长时间获取不到锁,导致饿死。

ReentrantLock中就有相关公平锁,非公平锁的实现了。

static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;

        /**
         * Performs lock.  Try immediate barge, backing up to normal
         * acquire on failure.
         */
        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }

    /**
     * Sync object for fair locks
     */
    static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

        final void lock() {
            acquire(1);
        }

        /**
         * Fair version of tryAcquire.  Don't grant access unless
         * recursive call or no waiters or is first.
         */
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                //hasQueuedPredecessors()  判断当前线程是否在同步队列的首位
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
    }
-------------------------------------------------------------------------------------
    final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

这两个类都继承了Sync,具体继承关系如下:

在这里插入图片描述

sync又继承了AbstractQueuedSynchronizer,也就是AQS。

通俗来讲

非公平锁就是当A线程准备进去获取锁,判断一下state值,如果是0那么就compareAndSetState(CAS)成功,并且修改当前锁为自己的,当B线程准备进去获取锁,判断state值为1,那就CAS失败,加入同步队列。此时如果A线程释放锁,并打算去唤醒B,结果C突然获取锁,判断state值恰好为0,则CAS成功,B判断state为1,又添加到同步队列了。

这就导致一个线程可能长时间无法得到资源,优点就是可能有的线程减少了等待时间,提高了利用率。

公平锁就是当A线程准备进去获取锁,判断一下state值,如果为0,然后进入同步队列看看是不是在首位,如果首位那么就CAS成功,并且修改当前锁为自己的,当B线程准备进去获取锁,判断state值为1,那就CAS失败,加入同步队列。此时如果A线程释放锁,并打算去唤醒B,结果C突然获取锁,判断state值恰好为0,但是去同步队列验证了一下不是首位,则CAS失败,线程B判断state值恰好为0,去同步队列验证了一下在是首位,则CAS成功,并且修改当前锁为自己的。

8.3 可重入锁

synchronized(隐式)和Lock(显式)都是可重入锁

隐式锁就是上锁释放锁都是自动的。

synchronized演示可重入锁

public class ThreadDemo03 {
    public static void main(String[] args) {
        Object o = new Object();
        new Thread(() -> {
            synchronized (o){
                System.out.println(Thread.currentThread().getName()+"上层");
                synchronized (o){
                    System.out.println(Thread.currentThread().getName()+"中层");
                    synchronized (o){
                        System.out.println(Thread.currentThread().getName()+"内层");
                    }
                }
            }
        },"t1").start();
    }
}

通俗来讲,可以在同步代码块嵌套使用同步代码块,再次使用锁对象。这就是可重入锁。

8.4 死锁

一个图解释一下死锁:

在这里插入图片描述

描述:当一个线程A持有锁A,线程B持有锁B,然后线程A试图获取锁B,线程B试图获取锁A 那么就发生了死锁。

产生死锁的原因:

  1. 系统资源不足
  2. 进程运行推进顺序不适合
  3. 资源分配不当

死锁代码演示:

public class ThreadDemo04 {
    static Object o1 = new Object();
    static Object o2 = new Object();

    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (o1){
                System.out.println(Thread.currentThread().getName()+"持有o1锁,等待获取o2锁");
                try {
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (o2){
                    System.out.println(Thread.currentThread().getName()+"已获取o2锁");
                }
            }
        },"A").start();

        new Thread(() -> {
            synchronized (o2){
                System.out.println(Thread.currentThread().getName()+"持有o2锁,等待获取o1锁");
                synchronized (o1){
                    System.out.println(Thread.currentThread().getName()+"已获取o1锁");
                }
            }
        },"B").start();
    }
}

9.JUC三大辅助类

一、减少计数CountDownLatch类

CountDownLatch类可以设置一个计数器,通过CountDown方法来进行减1的操作,使用await方法等待计数器不大于0,然后继续执行await()方法之后的语句。

案例:

public class ThreadDemo05 {
    //6个同学陆续离开教室之后,班长锁门
    public static void main(String[] args) {
        //1. 创建CountDownLatch对象,设置初始值
        CountDownLatch countDownLatch = new CountDownLatch(6);

        for (int i = 1; i <=6 ; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName()+"号同学离开了教室");
                //2. 计数  -1
                countDownLatch.countDown();
            },String.valueOf(i)).start();
        }

        //3. 等待
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"班长锁门走人了");
    }
}

二、循环栅栏CyclicBarrier类

CyclicBarrier的构造方法第一个参数是目标障碍数,每次执行CyclicBarrier一次,障碍数加1,如果达到了目标障碍数,才会执行CyclicBarrier.await()之后的语句。可以将CyclicBarrier理解为加1操作。

三、信号灯Semaphore类

有两个重要的方法:

1、semaphore.acquire()

请求一个信号量,这时候信号量个数-1,当减少到0的时候,下一次acquire不会再执行,只有当执行一个release()的时候,信号量不为0的时候才可以继续执行acquire

2、semaphore.release()

释放一个信号量,这时候信号量个数+1

此类设计的目的就是保证所有线程中只有指定的线程个数才能执行。

10.ReentrantReadWriteLock读写锁

首先说一个乐观锁和悲观锁:

  1. 悲观锁

通俗来讲,悲观锁是以悲观的态度来防止一切数据冲突,当一个线程修改数据之前,加锁,然后再进行读写,在读写过程中,其他线程处于等待状态,直到锁释放,其他线程才可以进入。

优点是保证了数据的准确性,缺点是在加锁锁释放锁会造成消耗,性能低。

  1. 乐观锁

乐观锁是对于数据冲突保持一种乐观态度,当线程操作数据的时候不会加锁,这使得多个任务可以并行的对数据进行操作,直到提交数据后,通过版本号对比的方式来验证数据是否存在冲突。

特点:乐观锁是一种并发类型的锁,其本身不对数据进行加锁通而是通过业务实现锁的功能,不对数据进行加锁就意味着允许多个请求同时访问数据,同时也省掉了对数据加锁和解锁的过程,这种方式因为节省了悲观锁加锁的操作,所以可以一定程度的的提高操作的性能,不过在并发非常高的情况下,会导致大量的请求冲突,冲突导致大部分操作无功而返而浪费资源,所以在高并发的场景下,乐观锁的性能却反而不如悲观锁。

其次表锁和行锁的概念:

顾名思义,表锁就是对整个表进行加锁,行锁是对表的一行记录加锁,表锁不会发生死锁,行锁会发生死锁。

最后,读锁和写锁。

读锁 也称 共享锁,写锁也称 独占锁。

一个资源可以同时被多个读线程访问,或者可以被一个写线程访问,但是不能同时存在读写线程,读写互斥

读与锁的演变:

无锁添加锁读写锁
多线程抢占资源使用synchronized和ReentrantLock 都是独占的,每次只能来一个操作ReentrantReadWriteLock 读读是可以共享,提升性能。缺点:1.造成锁饥饿,一直读,没有写操作。2.读的时候不能写,只有读完成之后才可以写,写操作的时候可以读。(锁降级: 将写入锁降级为读锁。)

锁降级: 获取写锁 --> 获取读锁 -->释放写锁 -->释放读锁, 注意读锁不能升级为写锁。

案例:

//创建资源类
class MyCache{

    //创建map集合
    private volatile Map<String,Object> map = new HashMap<>();


    //创建读写锁对象 --->没有加锁的时候,出现的问题就是没有写完就正在读了。
    private ReadWriteLock rwlock = new ReentrantReadWriteLock();

    //放数据
    public void put(String key, Object value){
        //上锁
        rwlock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName()+" 正在写操作"+key);
            Thread.sleep(300);

            map.put(key,value);

            System.out.println(Thread.currentThread().getName()+"写完了"+ key);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            //释放锁
            rwlock.writeLock().unlock();
        }

    }

    public Object get(String key){

        rwlock.writeLock().lock();
        Object result = null;
        try {

            System.out.println(Thread.currentThread().getName()+"正在读取操作" + key);
            Thread.sleep(300);
            result = map.get(key);
            System.out.println(Thread.currentThread().getName()+"已经取完了" + key);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            rwlock.writeLock().unlock();
        }

        return result;
    }
}
public class ThreadDemo06 {
    public static void main(String[] args) {
        MyCache myCache = new MyCache();

        //创建线程放数据
        for (int i = 1; i <=5; i++) {
            final int num = i;
            new Thread(() -> {
                myCache.put(num+"",num);
            },String.valueOf(i)).start();
        }

        //创建线程放数据
        for (int i = 1; i <=5; i++) {
            final int num = i;
            new Thread(() -> {
                myCache.get(num+"");
            },String.valueOf(i)).start();
        }
    }
}

运行结果:

1 正在写操作1
1写完了1
2 正在写操作2
2写完了2
3 正在写操作3
3写完了3
4 正在写操作4
4写完了4
5 正在写操作5
5写完了5
1正在读取操作1
2正在读取操作2
3正在读取操作3
4正在读取操作4
5正在读取操作5
3已经取完了3
1已经取完了1
2已经取完了2
4已经取完了4
5已经取完了5

从结果中我们也可以看出,读锁 也称 共享锁,写锁也称 独占锁。 写的时候一个线程写完然后另一个线程才可以写,读的时候可以并发地读。

11.堵塞队列

11.1 概述

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TQjr3Ttw-1648988389128)(JUC.assets/image-20220402162544790.png)]

  • 当队列是空的,从队列获取元素的操作将会被堵塞
  • 当队列是满的,从队列中添加元素的操作将会被堵塞

在多线程领域:在某些情况下会挂起线程(即堵塞),一旦条件满足,被挂起的线程又会自动被唤醒。

为什么需要BlockingQueue?

好处是我们不需要什么时候需要堵塞线程,什么时候需要唤醒线程,因为这一切BlockingQueue都给你一手包办了

11.2 架构

在这里插入图片描述

11.3 常用堵塞队列

ArrayBlockingQueue 由数组结构组成的有界堵塞队列

LinkedBlockingQueue 由链表接口组成的有界堵塞队列

方法:

方法类型抛出异常特殊值堵塞超时
插入add(e)offer(e)put(e)offer(e, time)
移除remove(e)poll(e)take(e)poll(e, time)
检查element(e)peek(e)不可用不可用

特殊值:插入方法,成功true 失败false。移除方法,成功返回队列元素,失败返回null

12.线程池

如果我们使用一个线程,就需要创建一个线程,这种模式虽然方便,但是频繁的创建线程销毁线程会浪费很多的时间。

所以我们可以引入线程池,将线程放入一个池子中,需要的时候就从池子里取,使用完后再放回池子中,这样就节省的时间以及提升了相应时间。

//演示 线程池三种分类
public class ThreadDemo07 {
    public static void main(String[] args) {
        //一池5线程
        ExecutorService threadpool1 = Executors.newFixedThreadPool(5);
        //一池一线程
        ExecutorService threadpool2 = Executors.newSingleThreadExecutor();
        //一池可扩容线程
        ExecutorService threadpool3 = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            threadpool1.execute(() ->{
                System.out.println(Thread.currentThread().getName()+"办理业务");
            });
        }
        threadpool1.shutdown();
    }
}

三种线程池底层都是由ThreadPoolExecutor实现的

public class ThreadPoolExecutor extends AbstractExecutorService {
    .....
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
            BlockingQueue<Runnable> workQueue);
 
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
            BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory);
 
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
            BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler);
 
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
        BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);
    ...
}

下面介绍一下七个参数:

  1. corePoolSize:核心线程数量(常驻) ---->举个例子,银行常用的有5个窗口(核心线程数量),如果人很多,那就把所有的窗口都开放(最大线程数量)
  2. maximumPoolSize:最大线程数量
  3. keepAliveTime:线程存活时间
  4. unit:线程存活单位 —> 3 是一个值, 这个是个单位
  5. BlockingQueue workQueue:堵塞队列
  6. ThreadFactory threadFactory:线程工厂
  7. RejectedExecutionHandler handler:拒绝策略

线程池底层工作流程:

在这里插入图片描述

通俗来讲,从图上可以看出,核心线程数量为2,最大线程数量为5,堵塞队列容量为3

当线程数量达到核心线程数量的时候,再来的线程就放到堵塞队列中,当堵塞队列也满了,就会开辟最大线程数量中其他线程(堵塞队列中的线程则继续等待),如果也达到最大线程数量,那么就会执行拒绝策略,这就是大体线程池工作流程。

拒接策略:

AbortPolicy:默认。直接抛出异常阻止系统正常运行

CallerRunsPolicy: 该策略不会抛弃任务,也不会抛出异常,而是将某些任务回退给调用者(从哪里来的回哪里去)

DiscardPolicy:不做任何处理

DiscardOldestPlicy:抛弃队列中等待最久的任务

自定义线程池:

public static void main(String[] args) {
    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
        1,
        5,
        2L,
        TimeUnit.SECONDS,
        new ArrayBlockingQueue<>(3),
        Executors.defaultThreadFactory(),
        new ThreadPoolExecutor.AbortPolicy());
}

13.Fork / join

Fork / join 可以将一个大的任务拆成多个子任务进行并行处理,最后将子任务结果合并成最后的计算结果,并输出。

Fork:把一个复杂任务进行拆分,大事化小

join:把拆分任务的结果进行合并

案例: 1+2+3…+100

class MyTask extends RecursiveTask<Integer>{
    private static final Integer VALUE = 10;
    private int begin;
    private int end;
    private int result =0;

    public MyTask(int begin, int end){
        this.begin = begin;
        this.end = end;
    }
    //拆分和合并过程
    @Override
    protected Integer compute() {
        //如果两个数的差值小于10,直接相加
        if((end - begin) <= VALUE){
            for(int i=begin;i<=end;i++){
                result = result + i;
            }
        }else{
            int middle = (begin+end)/2;
            //拆分左边
            MyTask task01 = new MyTask(begin,middle);
            //拆分右边
            MyTask task02 = new MyTask(middle+1,end);

            task01.fork();
            task02.fork();

            result = task01.join() + task02.join();

        }
        return result;
    }
}
public class ThreadDemo09 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyTask myTask = new MyTask(0,100);

        ForkJoinPool forkJoinPool =new ForkJoinPool();

        ForkJoinTask<Integer> forkJoinTask = forkJoinPool.submit(myTask);

        Integer result = forkJoinTask.get();

        System.out.println(result);

        //关闭
        forkJoinPool.shutdown();
    }
}

14.异步回调

我们常用的一些请求都是同步回调的,同步回调是阻塞的,单个的线程需要等待结果的返回才能继续执行。

异步回调通俗来讲,不需要等待被调用方法的结果,可以继续执行。

(待深入)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序猿研究僧

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值