可口的JAVA-并发控制之CountDownLatch
前言
本文帶大家系統的學習一下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;@Overridepublic 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);//調用服務ASystem.out.println("進入api");System.out.println("調用A服務");FutureTask<Integer> ft1 = new FutureTask<>(new BaseService(latch));new Thread(ft1,"底層服務A").start();//調用服務BSystem.out.println("調用B服務");FutureTask<Integer> ft2 = new FutureTask<>(new BaseService(latch));new Thread(ft2,"底層服務B").start();//調用服務CSystem.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;}@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()+"服務執行完畢,返回【"+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;@Overridepublic 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有區別。
總結
以上是生活随笔為你收集整理的可口的JAVA-并发控制之CountDownLatch的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 阿里为什么建议给MVC三层架构多加一层M
- 下一篇: JVM、GC看这一篇就够了!