Java併發同步之CountDownLatch、CyclicBarrier和Semaphore

CountDownLatch

直譯過來就是倒計數(CountDown)門閂(Latch)。倒計數不用說,門閂的意思顧名思義就是阻止前進。在這裏就是指 CountDownLatch.await() 方法在倒計數爲0之前會阻塞當前線程。

作用

CountDownLatch 的作用和 Thread.join() 方法類似,可用於一組線程和另外一組線程的協作。例如,主線程在做一項工作之前需要一系列的準備工作,只有這些準備工作都完成,主線程才能繼續它的工作。這些準備工作彼此獨立,所以可以併發執行以提高速度。在這個場景下就可以使用 CountDownLatch 協調線程之間的調度了。在直接創建線程的年代(Java 5.0 之前),我們可以使用 Thread.join()。在 JUC 出現後,因爲線程池中的線程不能直接被引用,所以就必須使用 CountDownLatch 了。

示例

下面的這個例子可以理解爲 F1 賽車的維修過程,只有 startSignal (可以表示停車,可能名字不太貼合)命令下達之後,維修工纔開始幹活,只有等所有工人完成工作之後,賽車才能繼續。

  1. class Driver { // ...  
  2.     void main() throws InterruptedException {  
  3.         CountDownLatch startSignal = new CountDownLatch(1);  
  4.         CountDownLatch doneSignal = new CountDownLatch(N);  
  5.  
  6.         for (int i = 0; i < N; ++i) // create and start threads  
  7.             new Thread(new Worker(startSignal, doneSignal)).start();  
  8.  
  9.         doSomethingElse();            // don't let run yet  
  10.         startSignal.countDown();      // let all threads proceed  
  11.         doSomethingElse();  
  12.         doneSignal.await();           // wait for all to finish  
  13.     }  
  14. }  
  15.  
  16. class Worker implements Runnable {  
  17.     private final CountDownLatch startSignal;  
  18.     private final CountDownLatch doneSignal;  
  19.     Worker(CountDownLatch startSignal, CountDownLatch doneSignal) {  
  20.         this.startSignal = startSignal;  
  21.         this.doneSignal = doneSignal;  
  22.     }  
  23.     public void run() {  
  24.         try {  
  25.             startSignal.await();  
  26.             doWork();  
  27.             doneSignal.countDown();  
  28.         } catch (InterruptedException ex) {} // return;  
  29.     }  
  30.  
  31.     void doWork() { ... }  

當 startSignal.await() 會阻塞線程,當 startSignal.countDown() 被調用之後,所有 Worker 線程開始執行 doWork() 方法,所以 Worker。doWork() 是幾乎同時開始執行的。當 Worker.doWork() 執行完畢後,調用 doneSignal.countDown(),在所有 Worker 線程執行完畢之後,主線程繼續執行。

CyclicBarrier

CyclicBarrier 翻譯過來叫循環柵欄、循環障礙什麼的(還是有點彆扭的。所以還是別翻譯了,只可意會不可言傳啊)。它主要的方法就是一個:await()。await() 方法沒被調用一次,計數便會減少1,並阻塞住當前線程。當計數減至0時,阻塞解除,所有在此 CyclicBarrier 上面阻塞的線程開始運行。在這之後,如果再次調用 await() 方法,計數就又會變成 N-1,新一輪重新開始,這便是 Cyclic 的含義所在。

CyclicBarrier 的使用並不難,但需要主要它所相關的異常。除了常見的異常,CyclicBarrier.await() 方法會拋出一個獨有的 BrokenBarrierException。這個異常發生在當某個線程在等待本 CyclicBarrier 時被中斷或超時或被重置時,其它同樣在這個 CyclicBarrier 上等待的線程便會受到 BrokenBarrierException。意思就是說,同志們,別等了,有個小夥伴已經掛了,咱們如果繼續等有可能會一直等下去,所有各回各家吧。

CyclicBarrier.await() 方法帶有返回值,用來表示當前線程是第幾個到達這個 Barrier 的線程。

和 CountDownLatch 一樣,CyclicBarrier 同樣可以可以在構造函數中設定總計數值。與 CountDownLatch 不同的是,CyclicBarrier 的構造函數還可以接受一個 Runnable,會在 CyclicBarrier 被釋放時執行。

  1. “NOTE: CyclicBarrier 的功能也可以由 CountDownLatch 來實現 

示例

CyclicBarrier 的應用(當然,這個例子換成 CountDownLatch 也是可以實現的,很簡單,就不說怎麼寫了)

  1. class Solver {  
  2.     final int N;  
  3.     final float[][] data;  
  4.     final CyclicBarrier barrier;  
  5.  
  6.     class Worker implements Runnable {  
  7.         int myRow;  
  8.         Worker(int row) { myRow = row; }  
  9.         public void run() {  
  10.             while (!done()) {  
  11.                 processRow(myRow);  
  12.  
  13.                 try {  
  14.                     barrier.await();  
  15.                 } catch (InterruptedException ex) {  
  16.                     return;  
  17.                 } catch (BrokenBarrierException ex) {  
  18.                     return;  
  19.                 }  
  20.             }  
  21.         }  
  22.     }  
  23.  
  24.     public Solver(float[][] matrix) {  
  25.         data = matrix;  
  26.         N = matrix.length;  
  27.         barrier = new CyclicBarrier(N, new Runnable() {  
  28.                 public void run() {  
  29.                     mergeRows(...);  
  30.                 }  
  31.             });  
  32.         for (int i = 0; i < N; ++i)  
  33.             new Thread(new Worker(i)).start();  
  34.  
  35.         waitUntilDone();  
  36.     }  

CyclicBarrier 和 CountDownLatch 在用法上的不同

CountDownLatch 適用於一組線程和另一個主線程之間的工作協作。一個主線程等待一組工作線程的任務完畢才繼續它的執行是使用 CountDownLatch 的主要場景;CyclicBarrier 用於一組或幾組線程,比如一組線程需要在一個時間點上達成一致,例如同時開始一個工作。另外,CyclicBarrier 的循環特性和構造函數所接受的 Runnable 參數也是 CountDownLatch 所不具備的。

Semaphore

Semaphore 直譯是信號量,可能稱它是許可量更容易理解。當然,因爲在計算機科學中這個名字由來已久,所以不能亂改。它的功能比較好理解,就是通過構造函數設定一個數量的許可,然後通過 acquire 方法獲得許可,release 方法釋放許可。它還有 tryAcquire 和 acquireUninterruptibly 方法,可以根據自己的需要選擇

示例:Semaphore 控制資源訪問

  1. class Pool {  
  2.     private static final int MAX_AVAILABLE = 100;  
  3.     private final Semaphore available = new Semaphore(MAX_AVAILABLE, true);  
  4.  
  5.     public Object getItem() throws InterruptedException {  
  6.         available.acquire();  
  7.         return getNextAvailableItem();  
  8.     }  
  9.  
  10.     public void putItem(Object x) {  
  11.         if (markAsUnused(x))  
  12.             available.release();  
  13.     }  
  14.  
  15.     // Not a particularly efficient data structure; just for demo  
  16.  
  17.     protected Object[] items = ... whatever kinds of items being managed  
  18.     protected boolean[] used = new boolean[MAX_AVAILABLE];  
  19.  
  20.     protected synchronized Object getNextAvailableItem() {  
  21.         for (int i = 0; i < MAX_AVAILABLE; ++i) {  
  22.             if (!used[i]) {  
  23.                 used[i] = true;  
  24.                 return items[i];  
  25.             }  
  26.         }  
  27.         return null// not reached  
  28.     }  
  29.  
  30.     protected synchronized boolean markAsUnused(Object item) {  
  31.         for (int i = 0; i < MAX_AVAILABLE; ++i) {  
  32.             if (item == items[i]) {  
  33.                 if (used[i]) {  
  34.                     used[i] = false;  
  35.                     return true;  
  36.                 } else 
  37.                     return false;  
  38.             }  
  39.         }  
  40.         return false;  
  41.     }  

上面這個示例中 Semaphore 的用法沒什麼可多講的。需要留言的是這裏面有兩個同步方法,不過對吞吐應該沒什麼影響,因爲主要是對一個 boolean 數組做一下 O(n) 的操作,而且每個循環裏面的操作很簡單,所以速度很快。不過不知道 JUC 裏面線程池的控制是怎麼做的,本人不才,還沒看過那塊源代碼,得空看看,有知道的也可以說說。

最後一句話總結

CountDownLatch 是能使一組線程等另一組線程都跑完了再繼續跑;CyclicBarrier 能夠使一組線程在一個時間點上達到同步,可以是一起開始執行全部任務或者一部分任務。同時,它是可以循環使用的;Semaphore 是隻允許一定數量的線程同時執行一段任務。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章