歡迎來到《併發王者課》,本文是該系列文章中的第20篇。
在上一篇文章中,我們介紹了Condition的用法。在本文中,將為你介紹CountDownLatch的用法。CountDownLatch是JUC中的一款常用工具類,當你在編寫多執行緒程式碼時,如果你需要協調多個執行緒的開始和結束動作時,它可能會是你的不錯選擇。
一、CountDownLatch適用的兩個典型應用場景
場景1. 協調子執行緒結束動作:等待所有子執行緒執行結束
對於資深的王者來說,下面這幅圖一定再熟悉不過了。在王者開局匹配隊友時,所有的玩家都必須進行確認操作,只有全部確認後才可以進入遊戲,否則將進行重新匹配。如果我們把各玩家看作是子執行緒的話,那麼就需要各子執行緒完成確認動作,遊戲才能繼續。其實,此類場景不止於王者,在生活中類似的場景還有很多。比如,所有乘客登機後飛機才能關艙門,除非超時後他們拋棄了你。
對於上圖所示的玩家確認介面,如果用多執行緒模擬的話,那麼它應該是下面的樣子:
主執行緒建立了5個子執行緒,各子任務執行確認動作,期間主執行緒進入等待狀態,直到各子執行緒的任務均已經完成,主執行緒恢復繼續執行,也就是遊戲繼續。而如果其中某個玩家超時未執行確認的話,那麼主執行緒將結束本次匹配,重新開始新一輪的匹配。
這個場景,就是CountDownLatch適用的第一個經典場景。
場景2. 協調子執行緒開始動作:統一各執行緒動作開始的時機
這個場景的例子也十分常見。比如,田徑場上,各選手各就各位等待發令槍。在發令槍響之前,選手只能原地就位,否則就是違規。如果從多執行緒的角度看,這恰似你建立了一些多執行緒,但是你需要統一管理它們的任務開始時間。因為,如果你不對此做干預的話,執行緒呼叫start()
之後的具體時間是不確定的,這個知識點我們早在青銅系列文章中就已經講過。
在王者中也有類似的場景,遊戲開始時,各玩家的初始狀態必須一致。總不能,你剛降生,對方已經打到你家門口了。
上述的兩個場景的問題,正是CountDownLatch所要解決的問題。理解了這兩個問題,你也就理解了CountDownLatch存在的價值。
二、Java中的CountDownLatch設計
JUC中CountDownLatch的實現,是以類的形式存在,而不是介面,你可以直接拿過來使用。並且,它的實現還很簡單。在資料結構上,CountDownLatch基於一個同步器實現,你可以看它的final Sync sync
變數。
而在建構函式上,CountDownLatch有且只有CountDownLatch(int count)
一個構造器,並且你需要指定數量,並且你不得在中途修改它,這點務必牢記!
核心函式
await()
:等待latch降為0;boolean await(long timeout, TimeUnit unit)
:等待latch降為0,但是可以設定超時時間。比如有玩家超時未確認,那就重新匹配,總不能為了某個玩家等到天荒地老吧。countDown()
:latch數量減1;getCount()
:獲取當前的latch數量。
從CountDownLatch的方法上看,還是比較簡單易懂的,重點要理解await()
和countDown()
.
三、CountDownLatch如何解決場景問題
接下來,我們將第一小節的兩個場景問題用程式碼實現一遍,讓你對CountDownLatch的用法有個直觀的理解。
場景1. CountDownLatch實現對各子執行緒的等待
建立大喬、蘭陵王、安其拉、哪吒和鎧等五個玩家,主執行緒必須在他們都完成確認後,才可以繼續執行。
在這段程式碼中,new CountDownLatch(5)
使用者建立初始的latch數量,各玩家通過countDownLatch.countDown()
完成狀態確認。
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(5);
Thread 大喬 = new Thread(countDownLatch::countDown);
Thread 蘭陵王 = new Thread(countDownLatch::countDown);
Thread 安其拉 = new Thread(countDownLatch::countDown);
Thread 哪吒 = new Thread(countDownLatch::countDown);
Thread 鎧 = new Thread(() -> {
try {
// 稍等,上個衛生間,馬上到...
Thread.sleep(1500);
countDownLatch.countDown();
} catch (InterruptedException ignored) {}
});
大喬.start();
蘭陵王.start();
安其拉.start();
哪吒.start();
鎧.start();
countDownLatch.await();
System.out.println("所有玩家已經就位!");
}
場景2. CountDownLatch實現對多執行緒的統一管理
在這個場景中,我們仍然用五個執行緒代表大喬、蘭陵王、安其拉、哪吒和鎧等五個玩家。需要注意的是,各玩家雖然都呼叫了start()
執行緒,但是它們在執行時都在等待countDownLatch的訊號,在訊號未收到前,它們不會往下執行。
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(1);
Thread 大喬 = new Thread(() -> waitToFight(countDownLatch));
Thread 蘭陵王 = new Thread(() -> waitToFight(countDownLatch));
Thread 安其拉 = new Thread(() -> waitToFight(countDownLatch));
Thread 哪吒 = new Thread(() -> waitToFight(countDownLatch));
Thread 鎧 = new Thread(() -> waitToFight(countDownLatch));
大喬.start();
蘭陵王.start();
安其拉.start();
哪吒.start();
鎧.start();
Thread.sleep(1000);
countDownLatch.countDown();
System.out.println("敵方還有5秒達到戰場,全軍出擊!");
}
private static void waitToFight(CountDownLatch countDownLatch) {
try {
countDownLatch.await(); // 在此等待訊號再繼續
System.out.println("收到,發起進攻!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
執行結果如下,各玩家在收到出擊訊號發起了進攻:
敵方還有5秒達到戰場,全軍出擊!
收到,發起進攻!
收到,發起進攻!
收到,發起進攻!
收到,發起進攻!
收到,發起進攻!
Process finished with exit code 0
小結
以上就是關於CountDownLatch的全部內容。總體上,CountDownLatch比較簡單且易於理解。在學習時,先了解其設計意圖,再寫個Demo基本就能流暢掌握。
正文到此結束,恭喜你又上了一顆星✨
夫子的試煉
- 編寫程式碼體驗CountDownLatch用法。
延伸閱讀與參考資料
最新修訂及更好閱讀體驗
關於作者
關注【技術八點半】,及時獲取文章更新。傳遞有品質的技術文章,記錄平凡人的成長故事,偶爾也聊聊生活和理想。早晨8:30推送作者品質原創,晚上20:30推送行業深度好文。
如果本文對你有幫助,歡迎點贊、關注、監督,我們一起從青銅到王者。