前言
本文带大家系统的学习一下CountDownLatch知识,从介绍、方法、场景、原理四个角度方面开展学习,话不多说,马上开始。
一:介绍
CountDownLatch是一种同步辅助工具,允许一个或多个线程等待*其他线程中正在执行的一组操作完成。
CountDownLatch 使用给定的 count 进行初始化。* {await} 方法阻塞,直到当前计数达到零,因为调用了 {countDown} 方法,之后*所有等待线程被释放,并且任何后续调用* {await } 立即返回。这是一种一次性现象,计数无法重置。如果您需要重置计数的版本,请考虑使用 CyclicBarrier。
二:方法
CountDownLatch类对外开放的方法有
- void await(); 启动线程等待
- void countDown(); 等待数目减一
- int getCount(); 获取当前等待线程数目
** 分享一段代码帮助大家理解**
以幼儿园呼叫孩子们做游戏为例子,老师要一个一个通知到孩子,孩子们签到,开始做游戏。 Flowerd.class
/** * @class 孩子 */ class Flowerd implements Runnable{ public Flowerd(CountDownLatch latch_s){ latch = latch_s; } private CountDownLatch latch; @Override public void run() { try { //1.孩子们反应时间不同,用一个随机数 Thread.sleep((int)(1000*Math.random())); System.out.println(Thread.currentThread().getName()+"::来了,还差【"+(latch.getCount()-1)+"】个人!"); latch.countDown(); } catch (InterruptedException e) { e.printStackTrace(); } } } 复制代码
playGame.method
/** * @function 喊孩子们做游戏 */ public static void playGame() { System.out.println("开始呼叫孩子们"); CountDownLatch downLatch = new CountDownLatch(3); System.out.println("呼叫小明。。。"); new Thread(new Flowerd(downLatch),"小明").start(); System.out.println("呼叫小滑。。。"); new Thread(new Flowerd(downLatch),"小滑").start(); System.out.println("呼叫小六。。。"); new Thread(new Flowerd(downLatch),"小六").start(); System.out.println("孩子们呼叫完毕"); try { downLatch.await(); System.out.println("孩子们都来了,开始做游戏吧。。。。"); } catch (InterruptedException e) { e.printStackTrace(); } } 复制代码
执行结果:
开始呼叫孩子们 呼叫小明。。。 呼叫小滑。。。 呼叫小六。。。 孩子们呼叫完毕 小滑::来了,还差【2】个人! 小六::来了,还差【1】个人! 小明::来了,还差【0】个人! 孩子们都来了,开始做游戏吧。。。。 复制代码
CountDownLatch初始化了三个孩子,主线程调用await阻塞,子线程调用countDown减一,当三个孩子全部报道后await方法自动释放。 另外,await方法支持设置定时器,超时自动释放。
boolean await(long timeout, TimeUnit unit) 复制代码
替换代码:
downLatch.await(300l, TimeUnit.MILLISECONDS); 复制代码
替换后执行结果:
开始呼叫孩子们 呼叫小明。。。 呼叫小滑。。。 呼叫小六。。。 孩子们呼叫完毕 小六::来了,还差【2】个人! 小滑::来了,还差【1】个人! 孩子们都来了,开始做游戏吧。。。。 小明::来了,还差【0】个人! 复制代码
三:场景
大家了解了CountDownLatchiben方法和特性后,可以进行合理推测:countdownlatch就是一把等待 锁,可以我等你也可以你等我,可以一个资源等多个资源,也可以多个资源等一个资源,延伸一下就是多个资源等多个资源。
场景一:一个资源等多个资源
某聚合接口的调用,需要整合底层多个服务调用结果,使用同步调用很显然不行(除非客户没有性能方面要求),异步调用后整合子服务结果返回。
public static void main(String[] args) { callAPI(); } /** * @function 模拟聚合接口调用 */ static void callAPI(){ CountDownLatch latch = new CountDownLatch(3); //调用服务A System.out.println("进入api"); System.out.println("调用A服务"); FutureTask<Integer> ft1 = new FutureTask<>(new BaseService(latch)); new Thread(ft1,"底层服务A").start(); //调用服务B System.out.println("调用B服务"); FutureTask<Integer> ft2 = new FutureTask<>(new BaseService(latch)); new Thread(ft2,"底层服务B").start(); //调用服务C System.out.println("调用C服务"); FutureTask<Integer> ft3 = new FutureTask<>(new BaseService(latch)); new Thread(ft3,"底层服务C").start(); try { System.out.println("等待子服务调用结果。。。"); latch.await(); System.out.println("API结果为:【"+(ft1.get()+ft2.get()+ft3.get())+"】"); } catch (InterruptedException e) { e.printStackTrace(); }catch (ExecutionException e){ e.printStackTrace(); } } 复制代码
/** * 可调用底层服务 */ class BaseService implements Callable<Integer>{ private CountDownLatch latch; public BaseService(CountDownLatch latch1){ latch = latch1; } @Override public Integer call() throws Exception { Thread.sleep((int)Math.random()*1000); Integer re = (int)(Math.random()*10000); latch.countDown(); System.out.println(Thread.currentThread().getName()+"服务执行完毕,返回【"+re+"】"); return re; } } 复制代码
执行结果:
进入api 调用A服务 调用B服务 调用C服务 等待子服务调用结果。。。 底层服务A服务执行完毕,返回【5643】 底层服务C服务执行完毕,返回【639】 底层服务B服务执行完毕,返回【3524】 API结果为:【9806】 复制代码
场景二:多个资源等一个资源
运动场上,选手准备完毕后,等待裁判发令枪响,然后同时起跑。 示例代码:
/** * 运动员 */ class RunMan implements Runnable{ public RunMan(CountDownLatch latch_s){ latch = latch_s; } private CountDownLatch latch; @Override public void run() { System.out.println("运动员【"+Thread.currentThread().getName()+"】准备完毕,等待裁判发令枪响。。。"); try { latch.await(); Thread.sleep((int)Math.random()*1000); System.out.println("运动员【"+Thread.currentThread().getName()+"】到达终点"); } catch (InterruptedException e) { e.printStackTrace(); } } } 复制代码
public static void main(String[] args) { raceArea(); } /** * 赛场 */ static void raceArea(){ System.out.println("开始准备"); CountDownLatch downLatch = new CountDownLatch(1); new Thread(new RunMan(downLatch),"小明").start(); new Thread(new RunMan(downLatch),"小滑").start(); new Thread(new RunMan(downLatch),"小六").start(); try { Thread.sleep(2000); System.out.println("裁判员开枪。。。。"); downLatch.countDown(); Thread.sleep(2000); System.out.println("比赛结束"); } catch (InterruptedException e) { e.printStackTrace(); } } 复制代码
执行结果:
开始准备 运动员【小滑】准备完毕,等待裁判发令枪响。。。 运动员【小六】准备完毕,等待裁判发令枪响。。。 运动员【小明】准备完毕,等待裁判发令枪响。。。 裁判员开枪。。。。 运动员【小滑】到达终点 运动员【小明】到达终点 运动员【小六】到达终点 比赛结束 复制代码
场景三:多个资源等多个资源
例如:汽车和电瓶车过马路,首先要保证是绿灯,其次要保证没有行人正在闯红灯横穿马路,我们可以设置两个线程(行人+绿灯)做等待条件,再设置两个线程(汽车和电瓶车)做要执行的条件,与上述两个功能都有类似之处,代码大家调整修改一下就OK了,搞不定的评论一下我再写。
四:部分原理
要点一:CountDownLatch内部使用了 AbstractQueuedSynchronizer(fifo)抽象同步队列的来保证同步。 源码01:
要点二:采用cas乐观锁控制CountDownLatch的递减,重写了tryReleaseShared()方法。源码02:
要点三:CountDownLatch是不可重复指定的,只能初始化一次,这点和CyclicBarrier有区别。