这次准备学习一个平常面试高频次会被提及的东东,属于线程之间的一种通信问题,这里先提一个问题:有两个线程,第一个线程需要等待第二个线程执行完所有的任务之后,第一个线程才能继续执行,那咱们可以使用线程中的哪个机制能实现呢?很显然利用Thread.join()方法,这是人人皆知的,但是还有一种场景是用Thread.join()解决不了的,具体啥场景,下面开始瞅一下,肯定是需要用到CoutDownLatch这个类来解决了。
CoutDownLatch使用场景:
先来看一个场景:启动了一个主服务,然后它下面会启动五个子服务,而当五个子服务都执行到一半的逻辑时【或者是特定条件成立时】则通知主服务可以继续往下执行了,而五个子服务没有执行到一半完成之前主服务只能等待。
很明显这种业务场景用Thread.join()是没法实现的,因为join()是一定要等待指定线程都执行消亡了调用方才能继续往下执行,看一下join()的api描述:
此时解决之道就是CoutDownLatch这个类了,先来简单看一下它的作用:
其中CountDownLatch是由两个单词构成:CoutDown倒数+Latch门闩。
CoutDownLatch示例分析:
接下来则针对上面的场景写一个针对性的示例,来展示一下CoutDownLatch的用法:
这个3代表啥呢?看一下API参数的说明:
其实也就是记数器值的最大大小,继续往下编写:
package com.javacurrency.test5;
import java.util.concurrent.CountDownLatch;
import java.util.stream.IntStream;
public class MyTest1 {
public static void main(String[] args) {
CountDownLatch countDownLatch = new CountDownLatch(3);
IntStream.range(0, 3).forEach(i -> new Thread(() -> {
try {
Thread.sleep(2000);
System.out.println("hello");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
countDownLatch.countDown();
}
}).start());
System.out.println("启动子线程完毕!");
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程执行完毕!");
}
}
下面先来看一下执行效果:
下面来分析一下这个程序:
先来对CoutDownLatch的它的特性说明一下:
而当CoutDownLatch中的计数器减为0时则不会再发生变化了,也就是说不会再继续减成负数了,也就是说假如我们创建了四个线程,而计数器设置的是3,最终CountDownLatch的计数器是不会减成-1的,这点需要注意!!那这计数器有啥用呢?其实主要是对这句话产生作用的:
会有啥作用呢?其实当调用coutDownLatch.await()方法时是会检测计数器的值的,当检测到计数器不为0,也就是它大于0,此时调用这个await()方法的线程就会进入阻塞队列当中;而如果计数器等于0了则该await()立马返回。所以基于这样的理论描述,输出结果也能解释了:
那貌似join()也可以对上面代码达到同样的效果呀,这是因为我们在子线程没有添加后续内容,如果说:
陷阱:
这玩意好不好,但是,有坑!!!假如没有调用指定计数器次数的coutDown()方法的话,那么await()方法则会始终阻塞了,啥意思,咱们演示一下:
还有一种可能就是线程数跟计数值是相等的,但是!!!程序这种也容易出坑:
所以最好的写法就是一定要把这个countDown()的执行放到finally中:
CoutDownLatch源码解读:
了解了CountDownLatch的基本使用之后,下面来稍加看一下它的底层实现,有助于加深对它的理解:
先来看一下await()方法:
先大致瞅一下它的说明:
而它底层是调用了一个sync,看一下:
接下来则来看一下它:
它被子类Sync覆写了:
此时再回到AbstractQueuedSynchronizer.acquireSharedInterruptibly()方法:
而如果计数器为0了,则不会执行它:
而await()方法还有一个超时的重载方法:
跟Object.wait()重载版本类似,咱们来试一下它:
接下来看一下countDown()方法的逻辑:
它的实现又是调用sync里面的方法,跟进去:
它又由子类覆盖了:
从这里也可以看出,不可能出现计数器减到负数的情况,因为到0则就直接返回了,继续:
这样再回到主流程上:
以上就是关于CountDownLatch的核心实现细节,这个类得好好消化,因为实际用到它的可能性还是比较大的。
关注个人公众号,获得实时推送