大白話說java併發工具類-CountDownLatch,CyclicBarrier

你聽___發表於2018-05-06

大白話說java併發工具類-CountDownLatch,CyclicBarrier

1. 倒數計時器CountDownLatch

在多執行緒協作完成業務功能時,有時候需要等待其他多個執行緒完成任務之後,主執行緒才能繼續往下執行業務功能,在這種的業務場景下,通常可以使用Thread類的join方法,讓主執行緒等待被join的執行緒執行完之後,主執行緒才能繼續往下執行。當然,使用執行緒間訊息通訊機制也可以完成。其實,java併發工具類中為我們提供了類似“倒數計時”這樣的工具類,可以十分方便的完成所說的這種業務場景。

為了能夠理解CountDownLatch,舉一個很通俗的例子,運動員進行跑步比賽時,假設有6個運動員參與比賽,裁判員在終點會為這6個運動員分別計時,可以想象沒當一個運動員到達終點的時候,對於裁判員來說就少了一個計時任務。直到所有運動員都到達終點了,裁判員的任務也才完成。這6個運動員可以類比成6個執行緒,當執行緒呼叫CountDownLatch.countDown方法時就會對計數器的值減一,直到計數器的值為0的時候,裁判員(呼叫await方法的執行緒)才能繼續往下執行。

下面來看些CountDownLatch的一些重要方法。

先從CountDownLatch的構造方法看起:

public CountDownLatch(int count)
複製程式碼

構造方法會傳入一個整型數N,之後呼叫CountDownLatch的countDown方法會對N減一,知道N減到0的時候,當前呼叫await方法的執行緒繼續執行。

CountDownLatch的方法不是很多,將它們一個個列舉出來:

  1. await() throws InterruptedException:呼叫該方法的執行緒等到構造方法傳入的N減到0的時候,才能繼續往下執行;
  2. await(long timeout, TimeUnit unit):與上面的await方法功能一致,只不過這裡有了時間限制,呼叫該方法的執行緒等到指定的timeout時間後,不管N是否減至為0,都會繼續往下執行;
  3. countDown():使CountDownLatch初始值N減1;
  4. long getCount():獲取當前CountDownLatch維護的值;

下面用一個具體的例子來說明CountDownLatch的具體用法:

public class CountDownLatchDemo {
private static CountDownLatch startSignal = new CountDownLatch(1);
//用來表示裁判員需要維護的是6個運動員
private static CountDownLatch endSignal = new CountDownLatch(6);

public static void main(String[] args) throws InterruptedException {
    ExecutorService executorService = Executors.newFixedThreadPool(6);
    for (int i = 0; i < 6; i++) {
        executorService.execute(() -> {
            try {
                System.out.println(Thread.currentThread().getName() + " 運動員等待裁判員響哨!!!");
                startSignal.await();
                System.out.println(Thread.currentThread().getName() + "正在全力衝刺");
                endSignal.countDown();
                System.out.println(Thread.currentThread().getName() + "  到達終點");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
    }
    System.out.println("裁判員發號施令啦!!!");
    startSignal.countDown();
    endSignal.await();
    System.out.println("所有運動員到達終點,比賽結束!");
    executorService.shutdown();
}
}
輸出結果:

pool-1-thread-2 運動員等待裁判員響哨!!!
pool-1-thread-3 運動員等待裁判員響哨!!!
pool-1-thread-1 運動員等待裁判員響哨!!!
pool-1-thread-4 運動員等待裁判員響哨!!!
pool-1-thread-5 運動員等待裁判員響哨!!!
pool-1-thread-6 運動員等待裁判員響哨!!!
裁判員發號施令啦!!!
pool-1-thread-2正在全力衝刺
pool-1-thread-2  到達終點
pool-1-thread-3正在全力衝刺
pool-1-thread-3  到達終點
pool-1-thread-1正在全力衝刺
pool-1-thread-1  到達終點
pool-1-thread-4正在全力衝刺
pool-1-thread-4  到達終點
pool-1-thread-5正在全力衝刺
pool-1-thread-5  到達終點
pool-1-thread-6正在全力衝刺
pool-1-thread-6  到達終點
所有運動員到達終點,比賽結束!
複製程式碼

該示例程式碼中設定了兩個CountDownLatch,第一個endSignal用於控制讓main執行緒(裁判員)必須等到其他執行緒(運動員)讓CountDownLatch維護的數值N減到0為止。另一個startSignal用於讓main執行緒對其他執行緒進行“發號施令”,startSignal引用的CountDownLatch初始值為1,而其他執行緒執行的run方法中都會先通過 startSignal.await()讓這些執行緒都被阻塞,直到main執行緒通過呼叫startSignal.countDown();,將值N減1,CountDownLatch維護的數值N為0後,其他執行緒才能往下執行,並且,每個執行緒執行的run方法中都會通過endSignal.countDown();endSignal維護的數值進行減一,由於往執行緒池提交了6個任務,會被減6次,所以endSignal維護的值最終會變為0,因此main執行緒在latch.await();阻塞結束,才能繼續往下執行。

另外,需要注意的是,當呼叫CountDownLatch的countDown方法時,當前執行緒是不會被阻塞,會繼續往下執行,比如在該例中會繼續輸出pool-1-thread-4 到達終點

2. 迴圈柵欄:CyclicBarrier

CyclicBarrier也是一種多執行緒併發控制的實用工具,和CountDownLatch一樣具有等待計數的功能,但是相比於CountDownLatch功能更加強大。

為了理解CyclicBarrier,這裡舉一個通俗的例子。開運動會時,會有跑步這一項運動,我們來模擬下運動員入場時的情況,假設有6條跑道,在比賽開始時,就需要6個運動員在比賽開始的時候都站在起點了,裁判員吹哨後才能開始跑步。跑道起點就相當於“barrier”,是臨界點,而這6個運動員就類比成執行緒的話,就是這6個執行緒都必須到達指定點了,意味著湊齊了一波,然後才能繼續執行,否則每個執行緒都得阻塞等待,直至湊齊一波即可。cyclic是迴圈的意思,也就是說CyclicBarrier當多個執行緒湊齊了一波之後,仍然有效,可以繼續湊齊下一波。CyclicBarrier的執行示意圖如下:

CyclicBarrier執行示意圖.jpg

當多個執行緒都達到了指定點後,才能繼續往下繼續執行。這就有點像報數的感覺,假設6個執行緒就相當於6個運動員,到賽道起點時會報數進行統計,如果剛好是6的話,這一波就湊齊了,才能往下執行。**CyclicBarrier在使用一次後,下面依然有效,可以繼續當做計數器使用,這是與CountDownLatch的區別之一。**這裡的6個執行緒,也就是計數器的初始值6,是通過CyclicBarrier的構造方法傳入的。

下面來看下CyclicBarrier的主要方法:

//等到所有的執行緒都到達指定的臨界點
await() throws InterruptedException, BrokenBarrierException 

//與上面的await方法功能基本一致,只不過這裡有超時限制,阻塞等待直至到達超時時間為止
await(long timeout, TimeUnit unit) throws InterruptedException, 
BrokenBarrierException, TimeoutException 

//獲取當前有多少個執行緒阻塞等待在臨界點上
int getNumberWaiting()

//用於查詢阻塞等待的執行緒是否被中斷
boolean isBroken()

	
//將屏障重置為初始狀態。如果當前有執行緒正在臨界點等待的話,將丟擲BrokenBarrierException。
void reset()
複製程式碼

另外需要注意的是,CyclicBarrier提供了這樣的構造方法:

public CyclicBarrier(int parties, Runnable barrierAction)
複製程式碼

可以用來,當指定的執行緒都到達了指定的臨界點的時,接下來執行的操作可以由barrierAction傳入即可。

一個例子

下面用一個簡單的例子,來看下CyclicBarrier的用法,我們來模擬下上面的運動員的例子。

public class CyclicBarrierDemo {
    //指定必須有6個運動員到達才行
    private static CyclicBarrier barrier = new CyclicBarrier(6, () -> {
        System.out.println("所有運動員入場,裁判員一聲令下!!!!!");
    });
    public static void main(String[] args) {
        System.out.println("運動員準備進場,全場歡呼............");

        ExecutorService service = Executors.newFixedThreadPool(6);
        for (int i = 0; i < 6; i++) {
            service.execute(() -> {
                try {
                    System.out.println(Thread.currentThread().getName() + " 運動員,進場");
                    barrier.await();
                    System.out.println(Thread.currentThread().getName() + "  運動員出發");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            });
        }
    }

}

輸出結果:
運動員準備進場,全場歡呼............
pool-1-thread-2 運動員,進場
pool-1-thread-1 運動員,進場
pool-1-thread-3 運動員,進場
pool-1-thread-4 運動員,進場
pool-1-thread-5 運動員,進場
pool-1-thread-6 運動員,進場
所有運動員入場,裁判員一聲令下!!!!!
pool-1-thread-6  運動員出發
pool-1-thread-1  運動員出發
pool-1-thread-5  運動員出發
pool-1-thread-4  運動員出發
pool-1-thread-3  運動員出發
pool-1-thread-2  運動員出發
複製程式碼

從輸出結果可以看出,當6個運動員(執行緒)都到達了指定的臨界點(barrier)時候,才能繼續往下執行,否則,則會阻塞等待在呼叫await()

3. CountDownLatch與CyclicBarrier的比較

CountDownLatch與CyclicBarrier都是用於控制併發的工具類,都可以理解成維護的就是一個計數器,但是這兩者還是各有不同側重點的:

  1. CountDownLatch一般用於某個執行緒A等待若干個其他執行緒執行完任務之後,它才執行;而CyclicBarrier一般用於一組執行緒互相等待至某個狀態,然後這一組執行緒再同時執行;CountDownLatch強調一個執行緒等多個執行緒完成某件事情。CyclicBarrier是多個執行緒互等,等大家都完成,再攜手共進。
  2. 呼叫CountDownLatch的countDown方法後,當前執行緒並不會阻塞,會繼續往下執行;而呼叫CyclicBarrier的await方法,會阻塞當前執行緒,直到CyclicBarrier指定的執行緒全部都到達了指定點的時候,才能繼續往下執行;
  3. CountDownLatch方法比較少,操作比較簡單,而CyclicBarrier提供的方法更多,比如能夠通過getNumberWaiting(),isBroken()這些方法獲取當前多個執行緒的狀態,並且CyclicBarrier的構造方法可以傳入barrierAction,指定當所有執行緒都到達時執行的業務功能;
  4. CountDownLatch是不能複用的,而CyclicLatch是可以複用的。

相關文章