一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。
用给定的计数 初始化 CountDownLatch
。由于调用了 countDown()
方法,所以在当前计数到达零之前,await
方法会一直受阻塞。之后,会释放所有等待的线程,await
的所有后续调用都将立即返回。这种现象只出现一次——计数无法被重置。如果需要重置计数,请考虑使用 CyclicBarrier
。CountDownLatch
是一个通用同步工具,它有很多用途。
将计数 1 初始化的 CountDownLatch
用作一个简单的开/关锁存器,或入口:在通过调用 countDown()
的线程打开入口前,所有调用 await
的线程都一直在入口处等待。
用 N 初始化的 CountDownLatch
可以使一个线程在 N 个线程完成某项操作之前一直等待,或者使其在某项操作完成 N 次之前一直等待。
场景一:运动员起跑
public class Demo02 {
public static void main(String[] args) {
int n=5;
//开关
CountDownLatch startSignal = new CountDownLatch(1);
//计数器
CountDownLatch doneSignal = new CountDownLatch(n);
for (int i = 0; i < n; ++i)
new Thread(new Worker(startSignal, doneSignal,i+"号选手")).start();
// try {
System.out.println("准备,3、2、1...");
//打开开关
startSignal.countDown();
System.out.println("开始!");
//doneSignal.await();
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
}
static class Worker implements Runnable{
private final CountDownLatch startSignal;
private final CountDownLatch doneSignal;
private final String name;
Worker(CountDownLatch startSignal, CountDownLatch doneSignal,String name) {
this.startSignal = startSignal;
this.doneSignal = doneSignal;
this.name=name;
}
@Override
public void run() {
try {
//开关关闭
startSignal.await();
doWork(name);
//计数器倒计时
doneSignal.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void doWork(String name){
System.out.println(name+"开始起跑...");
}
}
}
第一个类是一个启动信号,在 driver 为继续执行 worker 做好准备之前,它会阻止所有的 worker 继续执行。
第二个类是一个完成信号,它允许 driver 在完成所有 worker 之前一直等待。
以上是偷懒的写法,以上两个countDownlatch,任意去掉一个都能执行的。
startsignal这个是作为开关的形式,每一个线程任务类里面都调用了await()方法,只有最后执行countDown(),所有的才会一起执行。
doneSignal是作为一种倒计时器的方式,只有所有的线程都到位了,Runnable里面调用了五次countDown(),即doneSignal从5减到了0,所有的线程才会执行。
开关这种的应该很容易理解,下面我把doneSignal这种计数器的方式拆出来,以便理解
public class Demo01 {
public static void main(String[] args) {
int a=5;
CountDownLatch signal=new CountDownLatch(a);
for (int i=0;i<a;i++){
//for循环必须调用五次countDown
new Thread(new Runner(signal,i+"号选手")).start();
}
try {
//让所有的线程先阻塞着,等countDown清零了,才能放行
signal.await();
System.out.println("比赛结束...");
} catch (InterruptedException e1) {
e1.printStackTrace();
}
}
static class Runner implements Runnable{
private final CountDownLatch doSignal ;
private final String runnerName;
Runner(CountDownLatch c,String name){
this.doSignal=c;
this.runnerName=name;
}
@Override
public void run() {
try {
prepare(runnerName);
//倒计时器
doSignal.countDown();
} catch (Exception e) {
e.printStackTrace();
}
}
public void prepare(String name){
System.out.println("运动员"+name+"抵达终点!");
}
}
}
CountDownLatch使用起来很简单,只要你知道await() countDown()的含义,那么以上两个案例理解起来就很容易了。
CountDownLatch源码剖析:
countDown的步骤解析:
当countDown将计数器减为0的时候,上面的结果会返回true,if()判断会进入到方法体里面,执行到doReleaseShared()方法。做一些同步器Sync节点Node的释放之类的操作。为共享模式发布操作——信号继承和确保传播。如果不为0,那么直接返回false,到此结束了,所有的方法阻塞着,等待继续countDown()。
await()过程解析:
计数器清零时候,不会走if()方法体,await()执行结束,继续向下走。这里讨论一下没有计数器没有清零时,返回-1的情况。
以上就是countDownLatch的countDown和await方法,大致流程。这个需要对AQS的同步器有一定的了解,本次源码解析,没有谈论同步器中Node节点的添加和释放,但是这几张图片也大致可以看到双向链表Node释放的痕迹。感兴趣的可以自己看下源码,和看下本系列第一节的AQS讲解。