继续简单总结一下Java中并发流程控制的工具类,这一篇小结一下CountDownLatch和CyclicBarrier
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。
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的内容