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

原創文章&經驗總結&從校招到 A 廠一路陽光一路滄桑java

詳情請戳www.codercc.commarkdown

1. 倒計時器 CountDownLatch

在多線程協做完成業務功能時,有時候須要等待其餘多個線程完成任務以後,主線程才能繼續往下執行業務功能,在這種的業務場景下,一般可使用 Thread 類的 join 方法,讓主線程等待被 join 的線程執行完以後,主線程才能繼續往下執行。固然,使用線程間消息通訊機制也能夠完成。其實,java 併發工具類中爲咱們提供了相似「倒計時」這樣的工具類,能夠十分方便的完成所說的這種業務場景。多線程

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

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

先從 CountDownLatch 的構造方法看起:oop

public CountDownLatch(int count)
複製代碼

構造方法會傳入一個整型數 N,以後調用 CountDownLatch 的countDown方法會對 N 減一,知道 N 減到 0 的時候,當前調用await方法的線程繼續執行。spa

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 的具體用法:code

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(); } } 輸出結果:orm

複製代碼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
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 &lt; 6; i++) {
        service.execute(() -&gt; {
            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();
            }
        });
    }
}
複製代碼

}

複製代碼ExecutorService service = Executors.newFixedThreadPool(6); for (int i = 0; i &lt; 6; i++) { service.execute(() -&gt; { 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 是不能複用的,而 CyclicBarrier 是能夠複用的。
相關文章
相關標籤/搜索