1、概述
谷歌直译:倒数计时
还有一些其他翻译:计数减小门闩,倒计时闩锁
CountDownLatch类所在的包路径: java.util.concurrent.CountDownLatch
一种同步辅助类,它允许一个或多个线程等待,直到在其他线程中执行的一组操作完成为止。
使用场景:在主线程中创建多个子线程,等待所有子线程执行完成之后,再切换到主线程等待位置并往下继续执行。
2、关键方法、函数
名称 | 描述 |
---|---|
await() | 导致当前线程等待,直到锁存器递减至零为止,除非该线程被中断。 |
boolean await(long timeout, TimeUnit unit) | 导致当前线程等待,直到锁存器计数到零为止,除非该线程被中断或经过了指定的等待时间。 |
countDown() | 减少锁存器的计数,如果计数达到零,则释放所有等待线程。 |
long getCount() | 返回当前计数。返回值为0时,此后所有等待线程被释放,主线程继续执行await()后的代码。 |
3、案例
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Created by Administrator on 2020/4/24.
*/
public class TestCountDownLatch {
private static ExecutorService es = Executors.newCachedThreadPool();
static boolean isRun = false;
public static void main(String[] args) {
startTask();
//如果线程池不再使用,可通过下面这句关闭线程池,释放资源。
//es.shutdown();
}
private static volatile CountDownLatch latchInstance;
private static CountDownLatch getInstance(final int count) {
if (null == latchInstance) {
synchronized (CountDownLatch.class) {
if (null == latchInstance) {
latchInstance = new CountDownLatch(count);
}
}
}
return latchInstance;
}
/**
* 启动轮询任务
*/
private static void startTask() {
int threadNum = 5;
latchInstance = getInstance(threadNum);
for (; ; ) {
//TODO 3、关键语句 为0,则说明所有的子线程执行完成,会执行.await();之后的语句
if (latchInstance.getCount() == 0) {
//latchInstance只能用一次,用完之后变为0 ;为0之后就无法再进行倒计数了,如果要继续倒计数需要重新实例化
latchInstance = null;
//重新实例化时可以设置新的线程数;因为有可能在分组请求场景时,第一组可能只需要5个线程,到了第二轮则只需要3个线程就足够了,节省资源开销。
latchInstance = getInstance(threadNum);
}
if (!isRun) {
isRun = true;
System.out.println("主线程开始执行…… ……");
//在主线程中循环重建threadNum个子线程
for (int i = 0; i < threadNum; i++) {
//往线程池扔子线程
es.execute(() -> {
try {
Thread.sleep(1000);
System.out.println("子线程:" + Thread.currentThread().getName() + "执行");
} catch (InterruptedException e) {
e.printStackTrace();
}
//TODO 1、关键语句 倒计数子线程执行完成情况
latchInstance.countDown();
});
}
try {
//TODO 2、关键语句 这句之后的代码(主线程)会等待所有子线程执行完成后才会继续往下执行。
latchInstance.await();
isRun = false;
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(threadNum + "个子线程都执行完毕,继续往下执行主线程 " + isRun);
//第一次,所有子线程执行完成之后,假设:为了节省资源,我将线程数减少到3个。这个地方可以做很多文章,threadNum 是一个动态值,可动态控制子线程数量
threadNum = 3;
}
}
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
案例中关键的几个地方:
latchInstance.countDown();
子线程完成一个,则计数器-1
latchInstance.await();
这句之后的代码,会等待所有子线程执行完成后,才会继续往下执行,否则一直处于等待状态。
if (latchInstance.getCount() == 0)
如果计数器变为0,则说明所有的子线程执行完成,会执行latchInstance.await();之后的语句
输出:
主线程开始执行…… ……
子线程:pool-1-thread-1执行
子线程:pool-1-thread-3执行
子线程:pool-1-thread-5执行
子线程:pool-1-thread-4执行
子线程:pool-1-thread-2执行
5个子线程都执行完毕,继续往下执行主线程 false
主线程开始执行…… ……
子线程:pool-1-thread-5执行
子线程:pool-1-thread-3执行
子线程:pool-1-thread-4执行
3个子线程都执行完毕,继续往下执行主线程 false
主线程开始执行…… ……
子线程:pool-1-thread-4执行
子线程:pool-1-thread-5执行
子线程:pool-1-thread-3执行
3个子线程都执行完毕,继续往下执行主线程 false
...
案例中通过单例模式来创建CountDownLatch实例。
4、总结
CountDownLatch 在创建实例时构造函数必须要传入一个子线程数量,且子线程数量的值需>0。
当 getCount() =0时,这时候你看到的只是一个普通多线程环境,所以需要重新传入子线程数量创建新CountDownLatch 实例。