可口的JAVA-并发控制之CountDownLatch
前言
本文帶大家系統(tǒng)的學(xué)習一下CountDownLatch知識,從介紹、方法、場景、原理四個角度方面開展學(xué)習,話不多說,馬上開始。
一:介紹
CountDownLatch是一種同步輔助工具,允許一個或多個線程等待*其他線程中正在執(zhí)行的一組操作完成。
CountDownLatch 使用給定的 count 進行初始化。* {await} 方法阻塞,直到當前計數(shù)達到零,因為調(diào)用了 {countDown} 方法,之后*所有等待線程被釋放,并且任何后續(xù)調(diào)用* {await } 立即返回。這是一種一次性現(xiàn)象,計數(shù)無法重置。如果您需要重置計數(shù)的版本,請考慮使用 CyclicBarrier。
二:方法
CountDownLatch類對外開放的方法有
- void await(); 啟動線程等待
- void countDown(); 等待數(shù)目減一
- int getCount(); 獲取當前等待線程數(shù)目
** 分享一段代碼幫助大家理解**
以幼兒園呼叫孩子們做游戲為例子,老師要一個一個通知到孩子,孩子們簽到,開始做游戲。 Flowerd.class
/*** @class 孩子*/ class Flowerd implements Runnable{public Flowerd(CountDownLatch latch_s){latch = latch_s;}private CountDownLatch latch;@Overridepublic void run() {try {//1.孩子們反應(yīng)時間不同,用一個隨機數(shù)Thread.sleep((int)(1000*Math.random()));System.out.println(Thread.currentThread().getName()+"::來了,還差【"+(latch.getCount()-1)+"】個人!");latch.countDown();} catch (InterruptedException e) {e.printStackTrace();}} } 復(fù)制代碼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();} } 復(fù)制代碼執(zhí)行結(jié)果:
開始呼叫孩子們 呼叫小明。。。 呼叫小滑。。。 呼叫小六。。。 孩子們呼叫完畢 小滑::來了,還差【2】個人! 小六::來了,還差【1】個人! 小明::來了,還差【0】個人! 孩子們都來了,開始做游戲吧。。。。 復(fù)制代碼CountDownLatch初始化了三個孩子,主線程調(diào)用await阻塞,子線程調(diào)用countDown減一,當三個孩子全部報道后await方法自動釋放。 另外,await方法支持設(shè)置定時器,超時自動釋放。
boolean await(long timeout, TimeUnit unit) 復(fù)制代碼替換代碼:
downLatch.await(300l, TimeUnit.MILLISECONDS); 復(fù)制代碼替換后執(zhí)行結(jié)果:
開始呼叫孩子們 呼叫小明。。。 呼叫小滑。。。 呼叫小六。。。 孩子們呼叫完畢 小六::來了,還差【2】個人! 小滑::來了,還差【1】個人! 孩子們都來了,開始做游戲吧。。。。 小明::來了,還差【0】個人! 復(fù)制代碼三:場景
大家了解了CountDownLatchiben方法和特性后,可以進行合理推測:countdownlatch就是一把等待 鎖,可以我等你也可以你等我,可以一個資源等多個資源,也可以多個資源等一個資源,延伸一下就是多個資源等多個資源。
場景一:一個資源等多個資源
某聚合接口的調(diào)用,需要整合底層多個服務(wù)調(diào)用結(jié)果,使用同步調(diào)用很顯然不行(除非客戶沒有性能方面要求),異步調(diào)用后整合子服務(wù)結(jié)果返回。
public static void main(String[] args) {callAPI(); }/*** @function 模擬聚合接口調(diào)用*/ static void callAPI(){CountDownLatch latch = new CountDownLatch(3);//調(diào)用服務(wù)ASystem.out.println("進入api");System.out.println("調(diào)用A服務(wù)");FutureTask<Integer> ft1 = new FutureTask<>(new BaseService(latch));new Thread(ft1,"底層服務(wù)A").start();//調(diào)用服務(wù)BSystem.out.println("調(diào)用B服務(wù)");FutureTask<Integer> ft2 = new FutureTask<>(new BaseService(latch));new Thread(ft2,"底層服務(wù)B").start();//調(diào)用服務(wù)CSystem.out.println("調(diào)用C服務(wù)");FutureTask<Integer> ft3 = new FutureTask<>(new BaseService(latch));new Thread(ft3,"底層服務(wù)C").start();try {System.out.println("等待子服務(wù)調(diào)用結(jié)果。。。");latch.await();System.out.println("API結(jié)果為:【"+(ft1.get()+ft2.get()+ft3.get())+"】");} catch (InterruptedException e) {e.printStackTrace();}catch (ExecutionException e){e.printStackTrace();} } 復(fù)制代碼 /*** 可調(diào)用底層服務(wù)*/ class BaseService implements Callable<Integer>{private CountDownLatch latch;public BaseService(CountDownLatch latch1){latch = latch1;}@Overridepublic Integer call() throws Exception {Thread.sleep((int)Math.random()*1000);Integer re = (int)(Math.random()*10000);latch.countDown();System.out.println(Thread.currentThread().getName()+"服務(wù)執(zhí)行完畢,返回【"+re+"】");return re;} } 復(fù)制代碼執(zhí)行結(jié)果:
進入api 調(diào)用A服務(wù) 調(diào)用B服務(wù) 調(diào)用C服務(wù) 等待子服務(wù)調(diào)用結(jié)果。。。 底層服務(wù)A服務(wù)執(zhí)行完畢,返回【5643】 底層服務(wù)C服務(wù)執(zhí)行完畢,返回【639】 底層服務(wù)B服務(wù)執(zhí)行完畢,返回【3524】 API結(jié)果為:【9806】 復(fù)制代碼場景二:多個資源等一個資源
運動場上,選手準備完畢后,等待裁判發(fā)令槍響,然后同時起跑。 示例代碼:
/*** 運動員*/ class RunMan implements Runnable{public RunMan(CountDownLatch latch_s){latch = latch_s;}private CountDownLatch latch;@Overridepublic void run() {System.out.println("運動員【"+Thread.currentThread().getName()+"】準備完畢,等待裁判發(fā)令槍響。。。");try {latch.await();Thread.sleep((int)Math.random()*1000);System.out.println("運動員【"+Thread.currentThread().getName()+"】到達終點");} catch (InterruptedException e) {e.printStackTrace();}} } 復(fù)制代碼 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("比賽結(jié)束");} catch (InterruptedException e) {e.printStackTrace();}} 復(fù)制代碼執(zhí)行結(jié)果:
開始準備 運動員【小滑】準備完畢,等待裁判發(fā)令槍響。。。 運動員【小六】準備完畢,等待裁判發(fā)令槍響。。。 運動員【小明】準備完畢,等待裁判發(fā)令槍響。。。 裁判員開槍。。。。 運動員【小滑】到達終點 運動員【小明】到達終點 運動員【小六】到達終點 比賽結(jié)束 復(fù)制代碼場景三:多個資源等多個資源
例如:汽車和電瓶車過馬路,首先要保證是綠燈,其次要保證沒有行人正在闖紅燈橫穿馬路,我們可以設(shè)置兩個線程(行人+綠燈)做等待條件,再設(shè)置兩個線程(汽車和電瓶車)做要執(zhí)行的條件,與上述兩個功能都有類似之處,代碼大家調(diào)整修改一下就OK了,搞不定的評論一下我再寫。
四:部分原理
要點一:CountDownLatch內(nèi)部使用了 AbstractQueuedSynchronizer(fifo)抽象同步隊列的來保證同步。 源碼01:
要點二:采用cas樂觀鎖控制CountDownLatch的遞減,重寫了tryReleaseShared()方法。源碼02:
要點三:CountDownLatch是不可重復(fù)指定的,只能初始化一次,這點和CyclicBarrier有區(qū)別。
總結(jié)
以上是生活随笔為你收集整理的可口的JAVA-并发控制之CountDownLatch的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 阿里为什么建议给MVC三层架构多加一层M
- 下一篇: Spring选择哪种注入方式