文章目录
前言
继续简单总结一下Java中并发流程控制的工具类,这一篇小结一下CountDownLatch和CyclicBarrier
CountDownLatch
Latch中文是门闩的意思,CountDown是倒数,倒数到0,门闩开启。从名称可以看出这个工具的使用场景。比如在某多多上拼团购物,5人成团,等到成团了才有资格购买。
通常CountDownLatch有两种常见的用法
1、一个线程等待其他多个线程倒数为0时,再进入下一步
上图形象表示了CountDownLatch的一种使用方法,由其他线程倒数到0了之后,图中的Ta线程才能继续进行下一步。
简单代码实例:模拟一个产品需要5个质检员完成质检才能发布
/**
* autor:liman
* createtime:2021/11/28
* comment:模拟工厂质检
* 一个产品的发布,需要5个人完成质检
*/
@Slf4j
public class CountDownLatchDemo01 {
public static void main(String[] args) {
CountDownLatch countDownLatch = new CountDownLatch(5);//需要5个人完成质检
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (int i = 0; i < 5; i++) {
final int number = i+1;
Runnable runnable=new Runnable(){
@Override
public void run() {
try {
//模拟质检的时间
long checkTime = (long) (Math.random() * 10000);
Thread.sleep(checkTime);
System.out.println("No."+number+"完成了质检,耗时:"+checkTime+"ms");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//一个线程完成质检,就调用countDown方法,直到CountDownLatch的数值为0,则主线程就可以执行
countDownLatch.countDown();
}
}
};
executorService.submit(runnable);
}
System.out.println("等待5个质检员检查完毕");
//主线程等待5个子线程完成质检,调用CountDownLatch的await方法
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("所有人都完成了工作,产品可以对外发布");
//停止线程池
//判断线程池是否停止
executorService.shutdown();
while(!executorService.isTerminated()){
//线程池没有执行完成任务,主线程就空转,直到线程池运行结束
}
}
}
2、多个线程等待一个线程倒数为0,再进行下一步。
这里通过一个比较复杂的综合实例来说明,如以下代码,模拟了一个运动长跑的简单实例,采用了两个CountDownLatch对象。相关代码解释已在注释中
/**
* autor:liman
* createtime:2021/11/28
* comment:模拟运动会的运动员跑步
* 多个运动员等待统一指令起跑
* 同时所有运动员都到终点的时候才能结束比赛
*/
@Slf4j
public class CountDownLatchDemo02 {
public static void main(String[] args) throws InterruptedException {
//初始化启动的CountDownLatch,指定个数为1
CountDownLatch beginCountDownLatch = new CountDownLatch(1);
//初始化结束的CountDownLatch,指定个数为5
CountDownLatch endCountDownLatch = new CountDownLatch(5);
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (int i = 0; i < 5; i++) {
final int no = i+1;
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("No."+no+"准备完毕,等待发令枪发令");
try {
beginCountDownLatch.await();//子线程会在这里阻塞,等待主线程发出指令
System.out.println("No."+no+"开始跑步");
long runTime = (long) (Math.random() * 10000);
Thread.sleep(runTime);//模拟随机的跑步时长,执行完毕之后,表示跑到终点
System.out.println("No."+no+"到达终点,耗时:"+runTime+"ms");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//到达终点,发出信号
endCountDownLatch.countDown();
}
}
};
executorService.submit(runnable);
}
//主线程其实就是裁判,模拟裁判准备工作
Thread.sleep(5000);
System.out.println("发令枪响,所有运动员开始跑步");
beginCountDownLatch.countDown();
endCountDownLatch.await();//主线程阻塞,裁判员继续等待所有运动员到达终点。
System.out.println("所有人都到终点,比赛结束");
//判断线程池是否停止
executorService.shutdown();
while(!executorService.isTerminated()){
//线程池没有执行完成任务,主线程就空转,直到线程池运行结束
}
}
}
上述实例中的等待5个运动员到达终点,才能结束比赛,这一点和第一种场景,有五个质检员都完成质检,产品才能发布是一样的。
基于上述两个常用的使用场景,其实CountDownLatch也支持一组线程等待另一组线程执行完毕后的情况。
需要注意的是:CountDownLatch是不能复用的,如果要重新计数,可以考虑用CyclicBarrier或者重新创建CountDownLatch。
CyclicBarrier
Cyclic中文为循环,Barrier是栅栏的意思,循环栅栏说明这个是可复用的。这个其实在实际的使用场景中是和CountDownLatch很相似的。
直接上实例吧
/**
* autor:liman
* createtime:2021/11/28
* comment:循环栅栏的实例
* 相比于CountDownLatch,CyclicBarrier是可重用的
*/
@Slf4j
public class CyclicBarrierDemo {
public static void main(String[] args) {
//实例化一个CyclicBarrier,第二个参数是所有的线程都到齐了之后,CyclicBarrier的操作
CyclicBarrier cyclicBarrier = new CyclicBarrier(5, new Runnable() {
@Override
public void run() {
System.out.println("所有人都到了,大家统一出发");
}
});
for (int i = 0; i < 10; i++) {
//启动每一个线程
new Thread(new Task(i, cyclicBarrier)).start();
}
}
static class Task implements Runnable {
private int id;
private CyclicBarrier cyclicBarrier;
public Task(int id, CyclicBarrier cyclicBarrier) {
this.id = id;
this.cyclicBarrier = cyclicBarrier;
}
@Override
public void run() {
System.out.println("线程" + id + "现在前往集合地点");
try {
Thread.sleep((long) (Math.random() * 10000));
System.out.println("线程" + id + "到了集合地点,开始等待其他线程到达");
cyclicBarrier.await();//等待其他线程到达。
System.out.println("线程" + id + "出发");//所有线程都就位了,就会一起执行这个代码
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
}
}
运行结果
二者的区别
二者使用场景很相似,但还是存在些许差异
1、二者作用不同。CyclicBarrier要等到固定数量的线程到达了指定位置才能继续执行(CountDownLatch好像也有这个作用),但是CountDownLatch只需要等待设定的数值为0之后,就可以执行,毕竟一个线程中也可以多次进行倒数,CountDownLatch关注的是事件,而CyclicBarrier关注的才是线程。
2、可重用性不同。CountDownLatch在倒数到0时,就不能再次使用了,除非新建实例,而CyclicBarrier是可重用的,上述CyclicBarrier的实例中,虽然CyclicBarrier初始化的数值为5,但是循环启动了10个线程,依旧可重新使用。
总结
简单梳理了一下二者的使用场景,后续开始总结AQS的内容