乾貨!CountDownLatch的使用場景

本文已參與好文召集令活動,點擊查看:後端、大前端雙賽道投稿,2萬元獎池等你挑戰!前端

image.png

放上面這張圖有兩個意思java

第一 預祝我國奧運健兒在東京奧運會上搏擊長空,再創佳績,爲國爭光!面試

第二 跟本節內容有關,上面這幅圖更有利於你理解本節內容。數據庫

CountDownLatch顧名思義

CountDownLatch,它是 JDK 提供的併發流程控制的工具類,它是在 java.util.concurrent 包下,在 JDK1.5 之後加入的。後端

Count - Down - Latchmarkdown

計數,關閉/向下,門閥 (英文很差,勿噴)併發

那就能夠理解爲,一道門檻,按照計數量一個一個安排進出。哈哈 這是我我的理解,下面舉個正常的例子,讓你恍然大悟。dom

舉例理解

  • 第一個例子,好比咱們去遊樂園坐激流勇進,有的時候遊樂園里人不是那麼多,這時,管理員會讓你稍等一下,等人坐滿了再開船,這樣的話能夠在必定程度上節約遊樂園的成本。座位有多少,就須要等多少人,這就是 CountDownLatch 的核心思想,等到一個設定的數值達到以後,才能出發。

image.png

這幅圖就很好理解了。ide

能夠看到,最開始 CountDownLatch 設置的初始值爲 3,而後 T0 線程上來就調用 await 方法,它的做用是讓這個線程開始等待,等待後面的 T一、T二、T3,它們每一次調用 countDown 方法,3 這個數值就會減 1,也就是從 3 減到 2,從 2 減到 1,從 1 減到 0,一旦減到 0 以後,這個 T0 就至關於達到了本身觸發繼續運行的條件,因而它就恢復運行了。函數

  • 第二個例子,就是文章之初放的那幅圖。

image.png

這個例子正好與第一個例子相反,第一個是等全部人到了,我纔開始。這個例子是等全部運動員跑到終點以後,裁判纔會宣佈中止。CountDownLatch就是裁判!

主要方法介紹

下面介紹一下 CountDownLatch 的主要方法。

  • 構造函數:public CountDownLatch(int count) {  };

它的構造函數是傳入一個參數,該參數 count 是須要倒數的數值。

  • await():調用 await() 方法的線程開始等待,直到倒數結束,也就是 count 值爲 0 的時候纔會繼續執行。

  • await(long timeout, TimeUnit unit):await() 有一個重載的方法,裏面會傳入超時參數,這個方法的做用和 await() 相似,可是這裏能夠設置超時時間,若是超時就再也不等待了。

  • countDown():把數值倒數 1,也就是將 count 值減 1,直到減爲 0 時,以前等待的線程會被喚起。

CountDownLatch使用場景01

一個線程等待其餘多個線程都執行完畢,再繼續本身的工做

在實際開發場景中,不少狀況下須要咱們初始化一系列的前置操做,好比數據庫先創建鏈接,全部bean都加載完畢,在這些準備條件都完成以前,是不能進行下一步工做的,因此這就是利用 CountDownLatch 的一個很好場景,咱們可讓應用程序的主線程在其餘線程都準備完畢以後再繼續執行。

咱們還拿運動員跑步這個場景用代碼模擬下。好比在比賽跑步時有 5 個運動員參賽,終點有一個裁判員,何時比賽結束呢?那就是當全部人都跑到終點以後,這至關於裁判員等待 5 個運動員都跑到終點,宣佈比賽結束!

public class RunDemo1 {

    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(5);
        ExecutorService service = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 5; i++) {
            final int no = i + 1;
            Runnable runnable = new Runnable() {

                @Override
                public void run() {
                    try {
                        Thread.sleep((long) (Math.random() * 10000));
                        System.out.println(no + "號運動員完成了比賽。");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        latch.countDown();
                    }
                }
            };
            service.submit(runnable);
        }
        System.out.println("等待5個運動員都跑完.....");
        latch.await();
        System.out.println("全部人都跑完了,比賽結束。");
    }
}
複製代碼

在這段代碼中,咱們新建了一個初始值爲 5 的 CountDownLatch,而後創建了一個固定 5 線程的線程池,用一個 for 循環往這個線程池中提交 5 個任務(5個運動員),每一個任務表明一個運動員,這個運動員會首先隨機等待一段時間,表明他在跑步,而後打印出他完成了比賽,在跑完了以後,一樣會調用 countDown 方法來把計數減 1。

以後咱們再回到主線程,主線程打印完「等待 5 個運動員都跑完」這句話後,會調用 await() 方法,表明讓主線程開始等待,在等待以前的那幾個子線程都執行完畢後,它纔會認爲全部人都跑完了比賽。這段程序的運行結果以下所示:

等待5個運動員都跑完.....
4號運動員完成了比賽。
3號運動員完成了比賽。
1號運動員完成了比賽。
5號運動員完成了比賽。
2號運動員完成了比賽。
全部人都跑完了,比賽結束。
複製代碼

能夠看出,直到 5 個運動員都完成了比賽以後,主線程纔會繼續,並且因爲子線程等待的時間是隨機的,因此各個運動員完成比賽的次序也是隨機的。

CountDownLatch使用場景02

多個線程等待某一個線程的信號,同時開始執行

這和第一個用法有點相反,咱們再列舉一個實際的場景,好比在運動會上,剛纔說的是裁判員等運動員,如今是運動員等裁判員。在運動員起跑以前都會等待裁判員發號施令,一聲令下運動員統一塊兒跑,咱們用代碼把這件事情描述出來,以下所示:

public class RunDemo2 {

    public static void main(String[] args) throws InterruptedException {
        System.out.println("運動員有5秒的準備時間");
        CountDownLatch countDownLatch = new CountDownLatch(1);
        ExecutorService service = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 5; i++) {
            final int no = i + 1;
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    System.out.println(no + "號運動員準備完畢,等待裁判員的發令槍");
                    try {
                        countDownLatch.await();
                        System.out.println(no + "號運動員開始跑步了");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
            service.submit(runnable);
        }
        Thread.sleep(5000);
        System.out.println("5秒準備時間已過,發令槍響,比賽開始!");
        countDownLatch.countDown();
    }
}
複製代碼

在這段代碼中,首先打印出了運動員有 5 秒的準備時間,而後新建了一個 CountDownLatch,其倒數值只有 1;接着,一樣是一個 5 線程的線程池,而且用 for 循環的方式往裏提交 5 個任務,而這 5 個任務在一開始時就讓它調用 await() 方法開始等待。

接下來咱們再回到主線程。主線程會首先等待 5 秒鐘,這意味着裁判員正在作準備工做,好比他會喊「各就各位,預備」這樣的話語;而後 5 秒以後,主線程會打印出「5 秒鐘準備時間已過,發令槍響,比賽開始」的信號,緊接着會調用 countDown 方法,一旦主線程調用了該方法,那麼以前那 5 個已經調用了 await() 方法的線程都會被喚醒,因此這段程序的運行結果以下:

運動員有5秒的準備時間
2號運動員準備完畢,等待裁判員的發令槍
1號運動員準備完畢,等待裁判員的發令槍
3號運動員準備完畢,等待裁判員的發令槍
4號運動員準備完畢,等待裁判員的發令槍
5號運動員準備完畢,等待裁判員的發令槍
5秒準備時間已過,發令槍響,比賽開始!
2號運動員開始跑步了
1號運動員開始跑步了
5號運動員開始跑步了
4號運動員開始跑步了
3號運動員開始跑步了
複製代碼

能夠看到,運動員首先會有 5 秒鐘的準備時間,而後 5 個運動員分別都準備完畢了,等待發令槍響,緊接着 5 秒以後,發令槍響,比賽開始,因而 5 個子線程幾乎同時開始跑步了。

總結

剛纔講了兩種用法,其實這兩種用法並非孤立的,甚至能夠把這兩種用法結合起來,好比利用兩個 CountDownLatch,第一個初始值爲多個,第二個初始值爲 1,這樣就能夠應對更復雜的業務場景了;

CountDownLatch 類在建立實例的時候,須要在構造函數中傳入倒數次數,而後由須要等待的線程去調用 await 方法開始等待,而每一次其餘線程調用了 countDown 方法以後,計數便會減 1,直到減爲 0 時,以前等待的線程便會繼續運行。

小貼士: 咱們在實際開發中還能夠在統計數據中應用,好比咱們的數據展板統計,後臺可能有若干個查詢統計,那就用此思想,將每一個耗時查詢放到線程池裏,一塊兒跑,讓該接口快速響應。

弦外之音

這是我在掘金的第七篇文章了。感謝閱讀,並但願之後持續關注,我會輸出更多技術乾貨,咱們共同進步!

之後可能會分爲幾大專題,相似於併發專題,源碼專題,面試專題等(只會分享乾貨)。

感興趣的能夠點擊我頭像查看歷史文章,每一篇都是乾貨哦!

點滴積累,點滴分享,咱們共同成長!

相關文章
相關標籤/搜索