[Java并发]CountDownLatch
CountDownLatch
CountDownLatch一般用作多线程倒计时计数器,强制它们等待其他一组(CountDownLatch的初始化决定)任务执行完成。
有一点要说明的是CountDownLatch初始化后计数器值递减到0的时候,不能再复原的,这一点区别于Semaphore,Semaphore是可以通过release操作恢复信号量的。
CountDownLatch 使用一个计数器,这个计数器初始化为一个给定的值。每次调用 countDown() 方法时,计数器的值就减一。当计数器的值减到零时,所有等待在这个 CountDownLatch 上的线程都会被唤醒并继续执行。
countDownLatch.await(countDownLatchTimeout, TimeUnit.MINUTES);
await(long timeout, TimeUnit unit) 方法
这个方法使当前线程等待,直到计数器到达零或者指定的等待时间过去。这个方法带有两个参数:
timeout:等待的最大时间。
unit:时间单位,可以是 TimeUnit 枚举中的一个值,例如 SECONDS、MINUTES 等。
CountDownLatch使用原理#
- 创建CountDownLatch并设置计数器值。
- 启动多线程并且调用CountDownLatch实例的countDown()方法。
- 主线程调用 await() 方法,这样主线程的操作就会在这个方法上阻塞,直到其他线程完成各自的任务,count值为0,停止阻塞,主线程继续执行。
public class Main {
public static void main(String[] args) {
//创建CountDownLatch并设置计数值,该count值可以根据线程数的需要设置
CountDownLatch countDownLatch = new CountDownLatch(10);
//创建线程池
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
cachedThreadPool.execute(() -> {
try {
System.out.println(Thread.currentThread().getName() + " 正在运行!");
} catch (Exception e) {
System.out.println("Exception: do something exception");
} finally {
//该线程执行完毕-1
System.out.println("计数器-1 = " + countDownLatch.getCount());
countDownLatch.countDown();
}
});
}
try {
System.out.println("主线程试图执行");
countDownLatch.await(1, TimeUnit.MINUTES);
System.out.println("主线程恢复执行");
} catch (InterruptedException e) {
System.out.println("Exception: await interrupted exception");
} finally {
System.out.println("主线程执行结束");
}
//若需要停止线程池可关闭;
cachedThreadPool.shutdown();
}
}
主线程试图执行
pool-1-thread-2 正在运行!
pool-1-thread-1 正在运行!
pool-1-thread-3 正在运行!
pool-1-thread-4 正在运行!
计数器-1 = 10
计数器-1 = 10
计数器-1 = 10
计数器-1 = 10
pool-1-thread-5 正在运行!
计数器-1 = 6
pool-1-thread-6 正在运行!
计数器-1 = 5
pool-1-thread-7 正在运行!
pool-1-thread-8 正在运行!
计数器-1 = 4
计数器-1 = 4
pool-1-thread-9 正在运行!
计数器-1 = 2
pool-1-thread-10 正在运行!
计数器-1 = 1
主线程恢复执行
主线程执行结束
CountDownLatch常用方法#
-
public void await() throws InterruptedException:调用await()方法的线程会被挂起,等待直到count值为0再继续执行。
-
public boolean await(long timeout, TimeUnit unit) throws InterruptedException:同await(),若等待timeout时长后,count值还是没有变为0,不再等待,继续执行。时间单位如下常用的毫秒、天、小时、微秒、分钟、纳秒、秒。
-
public void countDown(): count值递减1.
-
public long getCount():获取当前count值。
-
public String toString():重写了toString()方法,多打印了count值,具体参考源码。
CountDownLatch使用场景#
一个程序中有N个任务在执行,我们可以创建值为N的CountDownLatch,当每个任务完成后,调用一下countDown()方法进行递减count值,再在主线程中使用await()方法等待任务执行完成,主线程继续执行。
CountDownLatch源码#
构造方法源码
/**
* Constructs a {@code CountDownLatch} initialized with the given count.
*
* @param count the number of times {@link #countDown} must be invoked
* before threads can pass through {@link #await}
* @throws IllegalArgumentException if {@code count} is negative
*/
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
toString()方法源码
/**
* Returns a string identifying this latch, as well as its state.
* The state, in brackets, includes the String {@code "Count ="}
* followed by the current count.
*
* @return a string identifying this latch, as well as its state
*/
public String toString() {
return super.toString() + "[Count = " + sync.getCount() + "]";
}
CountDownLatch示例#
作为线程启动信号#
public class Main {
public static void main(String[] args) throws InterruptedException {
CountDownLatch startSignal = new CountDownLatch(1);
CountDownLatch doneSignal = new CountDownLatch(10);
for (int i = 0; i < 10; i++) {
// create and start threads
new Thread(new Worker(startSignal, doneSignal)).start();
}
System.out.println("主线程将countdown置为0");
// 主线程发令
startSignal.countDown();
System.out.println("主线程等待十个线程完成");
doneSignal.await();
System.out.println("主线程恢复执行");
}
static class Worker implements Runnable {
private final CountDownLatch startSignal;
private final CountDownLatch doneSignal;
Worker(CountDownLatch startSignal, CountDownLatch doneSignal) {
this.startSignal = startSignal;
this.doneSignal = doneSignal;
}
@Override
public void run() {
try {
// 所有子线程等待countdown发令
System.out.println(Thread.currentThread().getName()+"子线程等待主线程发令");
startSignal.await();
doWork();
doneSignal.countDown();
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
void doWork() {
System.out.println(Thread.currentThread().getName()+"-执行中");
}
}
}
主线程将countdown置为0
主线程等待十个线程完成
Thread-1子线程等待主线程发令
Thread-1-执行中
Thread-0子线程等待主线程发令
Thread-0-执行中
Thread-2子线程等待主线程发令
Thread-2-执行中
Thread-3子线程等待主线程发令
Thread-3-执行中
Thread-4子线程等待主线程发令
Thread-4-执行中
Thread-5子线程等待主线程发令
Thread-5-执行中
Thread-7子线程等待主线程发令
Thread-7-执行中
Thread-8子线程等待主线程发令
Thread-8-执行中
Thread-9子线程等待主线程发令
Thread-9-执行中
Thread-6子线程等待主线程发令
Thread-6-执行中
主线程恢复执行
从运行结果可以看出:
主线程先打印 主线程将countdown置为0 和 主线程等待十个线程完成 。因为startSignal.countDown();完后,count才为0,子线程才能打印。
因为startSignal.await();是在子线程内,所有子线程都等待startSignal.countDown()执行后才能打印do work!。
doneSignal.await();等待所有子线程执行后,每次都doneSignal.countDown(),最后count为0,主线程才执行打印wait for all to finsh。
作为线程等待完成信号#
public class Main {
public static void main(String[] args) throws InterruptedException {
CountDownLatch doneSignal = new CountDownLatch(5);
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
cachedThreadPool.execute(new Worker(doneSignal));
}
System.out.println("主线程等待执行");
doneSignal.await();
System.out.println("主线程恢复执行");
cachedThreadPool.shutdown();
}
static class Worker implements Runnable{
private final CountDownLatch doneSignal;
Worker(CountDownLatch doneSignal) {
this.doneSignal = doneSignal;
}
@Override
public void run() {
try {
doWork();
doneSignal.countDown();
} catch (Exception ex) {
ex.printStackTrace();
}
}
void doWork() {
System.out.println(Thread.currentThread().getName() + "---执行---");
}
}
}
运行结果
主线程等待执行
pool-1-thread-2---执行---
pool-1-thread-1---执行---
pool-1-thread-3---执行---
pool-1-thread-4---执行---
pool-1-thread-5---执行---
主线程恢复执行
pool-1-thread-6---执行---
pool-1-thread-7---执行---
pool-1-thread-8---执行---
pool-1-thread-9---执行---
pool-1-thread-10---执行---
从运行结果可以看出,主线程是等待其他线程运行了5次结束后就打印了 主线程恢复执行 信息,因为CountDownLatch数值为5。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性