一、CountDownLatch是什么?
CountDownLatch是一个同步工具类,在java.util.cucurrent包中,是JUC编程中较为常用的一个工具类,允许一个或多个线程一直等待,直到其他线程运行完成后再执行。
它的的实现简单来说是通过一个计数器,初始化的时候给计时器一个指定值,然后在子线程中当执行完规定的逻辑后,计数器会进行减1操作,当计数器为0时,那么在阻塞等待的线程则会被唤醒恢复执行。
二、使用示例
CountDownLatch的使用一般可分为两种场景:
- 场景1:多个线程等待另一个
- 场景2:一个等待多个线程
1、场景1
这个就相当于是一组线程在准备就绪后,等待某个指定的时刻一起并发执行(秒杀),就像我们上高中时,早自习结束铃声一响,全体立马抄家伙冲向食堂~
public class Main4 {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(1);
// 简单直接的new线程了,也可以用线程池
new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + ":民以食为天,我等...");
countDownLatch.await(); // 阻塞,等待计数器值为0后再往下执行
System.out.println(Thread.currentThread().getName() + ":走着~");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"傻蛋").start();
new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + ":激动的心,颤抖的手,我也等...");
countDownLatch.await(); // 阻塞,等待计数器值为0后再往下执行
System.out.println(Thread.currentThread().getName() + ":食堂,我来了~");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"狗蛋").start();
new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + ":兄弟们,准备抄家伙...");
countDownLatch.await(); // 阻塞,等待计数器值为0后再往下执行
System.out.println(Thread.currentThread().getName() + ":冲鸭~");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"毛蛋").start();
// 主线程休眠1秒后,计数器减1
Thread.sleep(1000);
countDownLatch.countDown();
System.out.println("铃声响起!");
}
}
执行结果:
傻蛋:民以食为天,我等…
狗蛋:激动的心,颤抖的手,我也等…
毛蛋:兄弟们,准备抄家伙…
铃声响起!
傻蛋:走着~
毛蛋:冲鸭~
狗蛋:食堂,我来了~
2、场景2
有的时候并发任务可能存在一些前后依赖关系,就好比我需要做一个当月的销售数据分析,那么我通过多个线程去不同的数据表读取数据,读取完后再进行统一分析处理。
public class Main5 {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(3);
new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + "读取订单数据...");
Thread.sleep(700);
System.out.println(Thread.currentThread().getName() + "读取完成!");
countDownLatch.countDown(); // 子线程执行完后,计数减1
} catch (InterruptedException e) {
e.printStackTrace();
}
},"t1").start();
new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + "读取支付数据...");
Thread.sleep(500);
System.out.println(Thread.currentThread().getName() + "读取完成!");
countDownLatch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"t2").start();
new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + "读取会员数据...");
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "读取完成!");
countDownLatch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"t3").start();
// 阻塞,等待所有线程执行完成后再往下执行
countDownLatch.await();
System.out.println("数据加载完成,开始分析");
}
}
运行结果:
t1读取订单数据…
t2读取支付数据…
t3读取会员数据…
t2读取完成!
t1读取完成!
t3读取完成!
数据加载完成,开始分析
三、小结
CountDownLatch看起来是有点类似join() 方法,但是用起来比join()更加灵活。CountDownLatch可以在n个线程中调用n次减1操作,也可以在一个线程中调用n次减1操作。但是CountDownLatch是一次性的,计算器的初始值只能被初始化一次,当CountDownLatch使用完毕后不能再次被使用,所以这也就有了CyclicBarrier。