並發王者課-鉑金7:整齊划一-CountDownLatch如何協調多線程的開始和結束


歡迎來到《並發王者課》,本文是該系列文章中的第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推送行業深度好文。

如果本文對你有幫助,歡迎點贊關注監督,我們一起從青銅到王者


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM