一、概念
闭锁或叫发令枪,CountDownLatch这个类能够使一个线程等待其他线程完成各自的工作后再执行。例如,应用程序的主线程希望在负责启动框架服务的线程已经启动所有的框架服务之后再执行。
CountDownLatch 是通过一个计数器来实现的,计数器的初始值为初始任务的数量。每当完成了一个任务后,计数器的值就会减1(CountDownLatch.countDown()方法)。当计数器值到达 0 时,它表示所有的已经完成了任务,然后在闭锁上等待 CountDownLatch.await()方法的线程就可以恢复执行任务。
应用场景:
实现最大的并行性:有时我们想同时启动多个线程,实现最大程度的并行性。例如,我们想测试一个单例类。如果我们创建一个初始计数为1 的
CountDownLatch,并让所有线程都在这个锁上等待,那么我们可以很轻松地完成测试。我们只需调用 一次countDown()方法就可以让所有的等待线程同时恢复执行。
开始执行前等待 n 个线程完成各自任务:例如应用程序启动类要确保在处理用户请求前,所有 N 个外部系统已经启动和运行了,例如处理 excel 中多个表单。
简单来说就是一等多,一被阻塞,等待其他线程吧CountDownLatch的值减为0,一久可以执行了。如下图:
图中的计数器CNT为5,那么绿的线程TW1,TW2就处于无限阻塞当中,一直等到CNT为0的时候才会执行。
线程Ta、Tb、Tc、Td线程是TW1、TW2等待的线程
TW1、TW2先执行然后阻塞。Ta、Tb、Tc、Td、开始执行,Td先对CNT减1,CNT=4。然后Tb减1,CNT=3。Td再减1,CNT=2。Ta再减,CNT=1。Tc减1,CNT=0。TW1、TW2从阻塞中释放,开始执行。
Ta、Tb、Tc、Td分别对CNT进行缩减,一个线程并不一定只能减少1,比如线程Td就做了两次缩减。
二、API
CountDownLatch类
// 构造方法,count是计数器的值
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
// 阻塞
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
// 限定时间阻塞, 超时我不等了
public boolean await(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
// 计数器减1
public void countDown() {
sync.releaseShared(1);
}
// 获取计数器当前值
public long getCount() {
return sync.getCount();
代码演示
public class UseCountDownLatch {
// 定义一CountDownLatch, 计数器初始化是6
static CountDownLatch latch = new CountDownLatch(6);
/**
* 初始化线程(被等待线程)
*/
private static class InitThread implements Runnable {
@Override
public void run() {
System.out.println("初始化线程Thread_" + Thread.currentThread().getId() + " 准备执行...计数器减1..但是我先睡5秒.");
SleepTools.second(5);
latch.countDown();
// 休眠1S, 测试业务线程和主线程是在count变为0后直接开始工作
SleepTools.second(1);
System.out.println("初始化线程_" + Thread.currentThread().getId() + " .....继续工作...我是主线程后面执行的...说明count=0, 主线程就跑了");
}
}
/**
* 业务线程等待latch的计数器为0完成
*/
private static class BusiThread implements Runnable {
@Override
public void run() {
try {
// 阻塞业务线程, 等到CountDownLatch计数器为0 是否释放锁?
latch.await(2, TimeUnit.SECONDS);
System.out.println("业务线程Thread_" + Thread.currentThread().getId() + " 2秒时间到了,我不等了" + latch.getCount());
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("业务线程Thread_" + Thread.currentThread().getId() + " 执行完毕-----");
}
}
public static void main(String[] args) throws InterruptedException {
System.out.println("CountDownLatch 计数器初始化值: " + latch.getCount());
new Thread(() -> {
SleepTools.ms(1);
System.out.println("初始化线程Thread_" + Thread.currentThread().getId() + " 准备让计数器减2......");
latch.countDown();
latch.countDown();
}).start();
SleepTools.ms(10);
System.out.println("当前计数器值" + latch.getCount());
//业务线程启动
new Thread(new BusiThread()).start();
// 被等待线程
for (int i = 0; i <= 3; i++) {
Thread thread = new Thread(new InitThread());
thread.start();
}
// 主线程被阻塞等待计数器为0
latch.await();
System.out.println("主线程继续工作........");
}
}
源码解析:
缺:源码解析-awiat后线程是怎么发现count计数器为0 ,底层是怎么实现的?