天天看點

Java多線程程式設計-(6)-兩種常用的線程計數器CountDownLatch和循環屏障CyclicBarrier

前幾篇:

Java多線程程式設計-(1)-線程安全和鎖Synchronized概念

Java多線程程式設計-(2)-可重入鎖以及Synchronized的其他基本特性

Java多線程程式設計-(3)-線程本地ThreadLocal的介紹與使用

Java多線程程式設計-(4)-線程間通信機制的介紹與使用

Java多線程程式設計-(5)-使用Lock對象實作同步以及線程間通信

倒計時CountDownLatch

CountDownLatch是一個非常實用的多線程控制工具類,稱之為“倒計時器”,它允許一個或多個線程一直等待,直到其他線程的操作執行完後再執行。

舉了例子:

我們知道的集齊七顆龍珠就可以召喚神龍,那我們就一起召喚一下,下邊我需要派7個人(7個線程)去分别去找這7顆不同的龍珠,每個人找到之後回來告訴我還需要等待的龍珠個數減1個,那麼當全部的人都找到龍珠之後,那麼我就可以召喚神龍了。

順便寫個代碼如下:

public class SummonDragonDemo {

    private static final int THREAD_COUNT_NUM = ;
    private static CountDownLatch countDownLatch = new CountDownLatch(THREAD_COUNT_NUM);

    public static void main(String[] args) throws InterruptedException {

        for (int i = ; i <= THREAD_COUNT_NUM; i++) {
            int index = i;
            new Thread(() -> {
                try {
                    System.out.println("第" + index + "顆龍珠已收集到!");
                    //模拟收集第i個龍珠,随機模拟不同的尋找時間
                    Thread.sleep(new Random().nextInt());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //每收集到一顆龍珠,需要等待的顆數減1
                countDownLatch.countDown();
            }).start();
        }
        //等待檢查,即上述7個線程執行完畢之後,執行await後邊的代碼
        countDownLatch.await();
        System.out.println("集齊七顆龍珠!召喚神龍!");
    }
}
           

運作結果如下:

第7顆龍珠已收集到!
第2顆龍珠已收集到!
第6顆龍珠已收集到!
第3顆龍珠已收集到!
第5顆龍珠已收集到!
第4顆龍珠已收集到!
第1顆龍珠已收集到!
集齊七顆龍珠!召喚神龍!
           

上述的執行結果可以看出,當配置設定的7個人(7個線程)分别找到龍珠之後,也就是所有的線程執行完畢,才可以召喚龍珠(執行countDownLatch.await()之後的代碼)。

注意:

(1)CountDownLatch的構造函數

7表示需要等待執行完畢的線程數量。

(2)在每一個線程執行完畢之後,都需要執行

countDownLatch.countDown()

方法,不然計數器就不會準确;

(3)隻有所有的線程執行完畢之後,才會執行

countDownLatch.await()

之後的代碼;

(4)可以看出上述代碼中CountDownLatch 阻塞的是主線程;

那麼,假如我們不是用計數器CountDownLatch的話,結果可想而知,示例如下:

public class SummonDragonDemo {

    private static final int THREAD_COUNT_NUM = ;

    public static void main(String[] args) throws InterruptedException {

        for (int i = ; i <= THREAD_COUNT_NUM; i++) {
            int index = i;
            new Thread(() -> {
                try {
                    System.out.println("第" + index + "顆龍珠已收集到!");
                    //模拟收集第i個龍珠,随機模拟不同的尋找時間
                    Thread.sleep(Thread.sleep(Math.abs(new Random().nextInt())););
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        }
        System.out.println("集齊七顆龍珠!召喚神龍!");
    }
}
           

執行結果如下:

第1顆龍珠已收集到!
集齊七顆龍珠!召喚神龍!
第2顆龍珠已收集到!
第3顆龍珠已收集到!
第4顆龍珠已收集到!
第5顆龍珠已收集到!
第6顆龍珠已收集到!
第7顆龍珠已收集到!
           

結果隻能呵呵了!

好啦!上邊說了一堆水話,下面說點官方的解釋:

CountDownLatch是在java1.5被引入的,它存在于java.util.concurrent包下。CountDownLatch這個類能夠使一個線程等待其他線程完成各自的工作後再執行。例如,應用程式的主線程希望在負責啟動架構服務的線程已經啟動所有的架構服務之後再執行。

CountDownLatch是通過一個計數器來實作的,計數器的初始值為線程的數量。每當一個線程完成了自己的任務後,計數器的值就會減1。當計數器值到達0時,它表示所有的線程已經完成了任務,然後在閉鎖上等待的線程就可以恢複執行任務。

Java多線程程式設計-(6)-兩種常用的線程計數器CountDownLatch和循環屏障CyclicBarrier

CountDownLatch.java類中定義的構造函數:

public CountDownLatch(int count) { ... }
           

構造器中的計數值(count)實際上就是閉鎖需要等待的線程數量。這個值隻能被設定一次,而且CountDownLatch沒有提供任何機制去重新設定這個計數值。

與CountDownLatch的第一次互動是主線程等待其他線程。主線程必須在啟動其他線程後立即調用CountDownLatch.await()方法。這樣主線程的操作就會在這個方法上阻塞,直到其他線程完成各自的任務。

其他N 個線程必須引用閉鎖對象,因為他們需要通知CountDownLatch對象,他們已經完成了各自的任務。這種通知機制是通過 CountDownLatch.countDown()方法來完成的;每調用一次這個方法,在構造函數中初始化的count值就減1。是以當N個線程都調用了這個方法,count的值等于0,然後主線程就能通過await()方法,恢複執行自己的任務。

CountDownLatch在實時系統中的使用場景

讓我們嘗試羅列出在java實時系統中CountDownLatch都有哪些使用場景。我所羅列的都是我所能想到的。如果你有别的可能的使用方法,請在留言裡列出來,這樣會幫助到大家。

(1)實作最大的并行性:有時我們想同時啟動多個線程,實作最大程度的并行性。例如,我們想測試一個單例類。如果我們建立一個初始計數為1的CountDownLatch,并讓所有線程都在這個鎖上等待,那麼我們可以很輕松地完成測試。我們隻需調用 一次countDown()方法就可以讓所有的等待線程同時恢複執行。

(2)開始執行前等待n個線程完成各自任務:例如應用程式啟動類要確定在處理使用者請求前,所有N個外部系統已經啟動和運作了。

(3)死鎖檢測:一個非常友善的使用場景是,你可以使用n個線程通路共享資源,在每次測試階段的線程數目是不同的,并嘗試産生死鎖。

循環屏障CyclicBarrier

CyclicBarrier是另一種多線程并發控制使用工具,和CountDownLatch非常類似,他也可以實作線程間的計數等待,但他的功能要比CountDownLatch更加強大一些。

CyclicBarrier 的字面意思是可循環使用(Cyclic)的屏障(Barrier)。它要做的事情是,讓一組線程到達一個屏障(也可以叫同步點)時被阻塞,直到最後一個線程到達屏障時,屏障才會開門,所有被屏障攔截的線程才會繼續幹活。

CyclicBarrier預設的構造方法是

CyclicBarrier(int parties)

,其參數表示屏障攔截的線程數量,每個線程調用

await

方法告訴CyclicBarrier我已經到達了屏障,然後目前線程被阻塞。

CyclicBarrier強調的是n個線程,大家互相等待,隻要有一個沒完成,所有人都得等着。

還接着上述“集齊七顆龍珠!召喚神龍”的故事。召喚神龍,需要7個法師去尋找龍珠,但這7個法師并不是一下子就能号召起來的,是以要等待召集齊7個法師,然後在秋名山頂燒香拜佛為這7位法師送行,讓他們同時出發,前往不同的地方尋找龍珠(敲黑闆:這是第一個屏障點),在這七位法師臨行時約定找到龍珠之後還回到這個地方等待其他法師找到龍珠之後一起去見我。幾年之後,第一個法師回來了,然後等待其他的法師。。。,最後所有的法師全部到齊(敲黑闆:這是第一個屏障點),然後組隊來找我召喚神龍。

示例代碼如下:

public class SummonDragonDemo {

    private static final int THREAD_COUNT_NUM = ;

    public static void main(String[] args) {

        //設定第一個屏障點,等待召集齊7位法師
        CyclicBarrier callMasterBarrier = new CyclicBarrier(THREAD_COUNT_NUM, new Runnable() {
            @Override
            public void run() {
                System.out.println("7個法師召集完畢,同時出發,去往不同地方尋找龍珠!");
                summonDragon();
            }
        });
        //召集齊7位法師
        for (int i = ; i <= THREAD_COUNT_NUM; i++) {
            int index = i;
            new Thread(() -> {
                try {
                    System.out.println("召集第" + index + "個法師");
                    callMasterBarrier.await();
                } catch (InterruptedException | BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }

    /**
     * 召喚神龍:1、收集龍珠;2、召喚神龍
     */
    private static void summonDragon() {
        //設定第二個屏障點,等待7位法師收集完7顆龍珠,召喚神龍
        CyclicBarrier summonDragonBarrier = new CyclicBarrier(THREAD_COUNT_NUM, new Runnable() {
            @Override
            public void run() {
                System.out.println("集齊七顆龍珠!召喚神龍!");
            }
        });
        //收集7顆龍珠
        for (int i = ; i <= THREAD_COUNT_NUM; i++) {
            int index = i;
            new Thread(() -> {
                try {
                    System.out.println("第" + index + "顆龍珠已收集到!");
                    summonDragonBarrier.await();
                } catch (InterruptedException | BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}
           

執行結果:

召集第2個法師
召集第1個法師
召集第3個法師
召集第4個法師
召集第6個法師
召集第5個法師
召集第7個法師
7個法師召集完畢,同時出發,去往不同地方尋找龍珠!
第1顆龍珠已收集到!
第2顆龍珠已收集到!
第3顆龍珠已收集到!
第4顆龍珠已收集到!
第5顆龍珠已收集到!
第6顆龍珠已收集到!
第7顆龍珠已收集到!
集齊七顆龍珠!召喚神龍!
           

代碼中設定了兩個屏障點,第一個用于召集7個法師,等7個法師召集完後,在設定在一個屏障點,7位法師去尋找龍珠,然後召喚神龍,中間有個嵌套的關系!

上述的例子,大緻說了一下屏障,因為設定了兩個屏障,并沒有示範上述說的可循環使用(Cyclic)的屏障(Barrier) 中的可循環使用(Cyclic)

/**
     * Resets the barrier to its initial state.  If any parties are
     * currently waiting at the barrier, they will return with a
     * {@link BrokenBarrierException}. Note that resets <em>after</em>
     * a breakage has occurred for other reasons can be complicated to
     * carry out; threads need to re-synchronize in some other way,
     * and choose one to perform the reset.  It may be preferable to
     * instead create a new barrier for subsequent use.
     */
    public void reset() { ... }
           

檢視

CyclicBarrier.reset()

可知,可以使CyclicBarrier回到最初始的狀态,由于使用的相對較少,這裡不再示範。

CyclicBarrier和CountDownLatch的差別

(1)CountDownLatch的計數器隻能使用一次。而CyclicBarrier的計數器可以使用reset() 方法重置。是以CyclicBarrier能處理更為複雜的業務場景,比如如果計算發生錯誤,可以重置計數器,并讓線程們重新執行一次。

(2)CyclicBarrier還提供其他有用的方法,比如getNumberWaiting方法可以獲得CyclicBarrier阻塞的線程數量。isBroken方法用來知道阻塞的線程是否被中斷。比如以下代碼執行完之後會傳回true。

(3)CountDownLatch會阻塞主線程,CyclicBarrier不會阻塞主線程,隻會阻塞子線程。

參考文章:

1、http://www.importnew.com/15731.html