小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。 本文已参与「掘力星计划」,赢取创作大礼包,挑战创作激励金。
注:掘金潜水怪今日起开更
注:老后端不水图
前言
本文带大家系统的学习一下CountDownLatch知识,从介绍、方法、场景、原理四个角度方面开展学习,话不多说,马上开始。
一:介绍
CountDownLatch是一种同步辅助工具,允许一个或多个线程等待*其他线程中正在执行的一组操作完成。
CountDownLatch 使用给定的 count 进行初始化。* {await} 方法阻塞,直到当前计数达到零,因为调用了 {countDown} 方法,之后*所有等待线程被释放,并且任何后续调用* {await } 立即返回。这是一种一次性现象,计数无法重置。如果您需要重置计数的版本,请考虑使用 CyclicBarrier。
二:方法
CountDownLatch类对外开放的方法有
** 分享一段代码帮助大家理解**
以幼儿园呼叫孩子们做游戏为例子,老师要一个一个通知到孩子,孩子们签到,开始做游戏。 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有区别。
欢迎大家提意见