并发工具类-CountDownLatch倒计时器
简介
一种同步帮助,它允许一个或多个线程等待,直到在其他线程中执行的一组操作完成为止。
一个CountDownLatch初始化为给定数。该await方法将阻塞,直到由于该countDown()方法的调用导致当前计数达到零为止,此后所有等待线程被释放。
CountDownLatch这个类能够使一个线程等待其他线程完成各自的工作后再执行。例 如,应用程序的主线程希望在负责启动框架服务的线程已经启动所有的框架服务之后再执行。
这是一种一次性现象-无法重置计数。如果您需要用于重置计数的版本,请考虑使用CyclicBarrier。
应用场景
比如某流水线上有3个工作人员(线程):worker1、worker2、worker3
worker3需要等到worker1和worker2的工作全部做完才能开始工作,但是worker1和worker2工作内容不同,无法保证其在同一时间点同时完成,所以需要worker3在另外两个线程没有全部完成之前,处于等待状态(休眠)
当worker1和worker2全部完成以后,worker3马上转为工作状态(唤醒)开始工作:
/**
* @auther CountDownLatchSimple
* @create xxxx/xx/xx
*/
public class CountDownLatchSimple {
/**
* 模拟工作场景
* @param args
* @throws InterruptedException
*/
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(2); // 初始化countDownLatch,设置计数器值为2
Thread worker1 = new Worker(countDownLatch,"worker1");
Thread worker2 = new Worker(countDownLatch,"worker2");
Thread worker3 = new Worker(countDownLatch,"worker3");
worker1.start(); // worker1开始工作
worker2.start(); // worker1开始工作
countDownLatch.await(); // 阻塞主线程,当计数器的值被减为0的时候,释放线程
worker3.start(); // worker1开始工作
}
static class Worker extends Thread {
private CountDownLatch countDownLatch;
public Worker(CountDownLatch countDownLatch,String workerName){
this.countDownLatch = countDownLatch;
this.setName(workerName);
}
@Override
public void run() {
try {
System.out.println(this.getName() + " - 开始工作");
Thread.sleep(2000); // 模拟工作过程
System.out.println(this.getName() + " - 工作完成");
countDownLatch.countDown(); // 工作完成,将countDownLatch中的计数器 -1
}catch (Exception e){
e.printStackTrace();
}
}
}
}
运行结果:
worker1 - 开始工作
worker2 - 开始工作
worker2 - 工作完成
worker1 - 工作完成
worker3 - 开始工作
worker3 - 工作完成
Process finished with exit code 0
CountDownLatch初始化时设置计数器值,主线程判断计数器值不为0,则休眠,当前正在运行的线程(worker1,worker2)在执行完毕后通过countDownLatch.countDown()
将计数器值-1
一旦计数器值归0,主线程则被唤醒,继续执行(启动worker3)
当然,次场景并非只能通过CountDownLatch实现,用join()同样可以实现:
/**
* @auther CountJoinSimple
* @create xxxx/xx/xx
*/
public class CountJoinSimple {
/**
* 模拟工作场景
* @param args
* @throws InterruptedException
*/
public static void main(String[] args) throws InterruptedException {
Thread worker1 = new Thread(new Worker(),"worker1");
Thread worker2 = new Thread(new Worker(),"worker2");
Thread worker3 = new Thread(new Worker(),"worker3");
worker1.start(); // worker1开始工作
worker2.start(); // worker1开始工作
worker1.join();
worker2.join();
worker3.start(); // worker1开始工作
}
static class Worker extends Thread {
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName() + " - 开始工作");
Thread.sleep(2000); // 模拟工作过程
System.out.println(Thread.currentThread().getName() + " - 工作完成");
}catch (Exception e){
e.printStackTrace();
}
}
}
}
运行结果:
worker1 - 开始工作
worker2 - 开始工作
worker2 - 工作完成
worker1 - 工作完成
worker3 - 开始工作
worker3 - 工作完成
Process finished with exit code 0
join()通过判断线程worker1和worker2的存活状态,如果线程存活,则当前线程wait():
if (active()){
worker3.wait();
}
CountDownLatch和join()的区别
如果以上场景稍微调整一下,则可以明显发现两者的不同之处:
如果worker3不是要等到前两个工作人员全部工作完成才能开始工作
而是:当worker1和worker2两个工作人员全部开始工作以后,worker3才可以开始工作,那么,CountDownLatch依然可以实现:
static class Worker extends Thread {
private CountDownLatch countDownLatch;
public Worker(CountDownLatch countDownLatch,String workerName){
this.countDownLatch = countDownLatch;
this.setName(workerName);
}
@Override
public void run() {
try {
System.out.println(this.getName() + " - 开始工作");
countDownLatch.countDown(); // 开始工作,将countDownLatch中的计数器 -1
Thread.sleep(2000); // 模拟工作过程
System.out.println(this.getName() + " - 工作完成");
}catch (Exception e){
e.printStackTrace();
}
}
}
运行结果:
worker2 - 开始工作
worker1 - 开始工作
worker3 - 开始工作
worker2 - 工作完成
worker3 - 工作完成
worker1 - 工作完成
Process finished with exit code 0
由此可见,只是调整了工作过程中countDownLatch.countDown()
的位置,便可以轻松实现。
可看出当前两种方法的区别:join()必须等thread执行完毕,当前线程才能继续往下执行,而CountDownLatch只是通过对计数器灵活的控制,只要检测道计数器数值为0,当前线程即可继续往下执行而不需要关注其他线程是否执行完毕。