1. 程式人生 > >並發工具類(一)等待多線程的CountDownLatch

並發工具類(一)等待多線程的CountDownLatch

err 更多 表示 amp throw new 所有 barrier let

前言

??JDK中為了處理線程之間的同步問題,除了提供鎖機制之外,還提供了幾個非常有用的並發工具類:CountDownLatch、CyclicBarrier、Semphore、Exchanger、Phaser;
??CountDownLatch、CyclicBarrier、Semphore、Phaser 這四個工具類提供一種並發流程的控制手段;而Exchanger工具類則提供了在線程之間交換數據的一種手段。

簡介

??CountDownLatch 允許一個或多個線程等待其他線程完成操作。單詞Latch的意思是“門閂”,所以沒有打開時,N個人是不能進入屋內的,也就是N個線程是不能往下執行的,從而控制線程執行任務的時機,使線程以“組團”的方式一起執行任務。
??CountDownLatch 類 在創建時,給定一個計數count。線程調用CountDownLatch 對象的awiat( )方法時,判斷這個計數count是否為0,如果不為0,就進入等待狀態。其他線程在完成一定任務時,調用CountDownLatch 的countDown()方法,使計數count減一。直到count的值等於0或者少於0時,便是等待線程的運行時機,將會繼續往下運行。

**CountDownLatch的API接口**
方法名稱 描 述
void await() 使當前線程在鎖存器倒計數至零之前一直等待,除非線程被中斷。
boolean await(long timeout, TimeUnit unit) 使當前線程在鎖存器倒計數至零之前一直等待,除非線程被中斷或超出了指定的等待時間。
void countDown() 遞減鎖存器的計數,如果計數到達零,則釋放所有等待的線程。
long getCount() 返回當前計數。
String toString() 返回標識此鎖存器及其狀態的字符串。

註意: await()也可以被多個線程同時調用,從而實現多個線程 等待其他的多個線程完成某部分操作。



下面是API文檔介紹的兩個經典用法:

@ Example1:下面給出了兩個類,其中一組 worker 線程使用了兩個倒計數鎖存器:

  • 第一個類是一個啟動信號,在 driver 為繼續執行 worker 做好準備之前,它會阻止所有的 worker 繼續執行。
  • 第二個類是一個完成信號,它允許 driver 在完成所有 worker 之前一直等待。
class Driver { // ...
   void main() throws InterruptedException {
     CountDownLatch startSignal = new CountDownLatch(1);
     CountDownLatch doneSignal = new
CountDownLatch(N); for (int i = 0; i < N; ++i) // create and start threads new Thread(new Worker(startSignal, doneSignal)).start(); doSomethingElse(); // don‘t let run yet startSignal.countDown(); // let all threads proceed doSomethingElse(); doneSignal.await(); // wait for all to finish } } class Worker implements Runnable { private final CountDownLatch startSignal; private final CountDownLatch doneSignal; Worker(CountDownLatch startSignal, CountDownLatch doneSignal) { this.startSignal = startSignal; this.doneSignal = doneSignal; } public void run() { try { startSignal.await(); doWork(); doneSignal.countDown(); } catch (InterruptedException ex) {} // return; } void doWork() { ... } }

@ Example2:另一種典型用法是,將一個問題分成 N 個部分,用執行每個部分並讓鎖存器倒計數的 Runnable 來描述每個部分,然後將所有 Runnable 加入到 Executor 隊列。當所有的子部分完成後,協調線程就能夠通過 await。(當線程必須用這種方法反復倒計數時,可改為使用 CyclicBarrier。)

class Driver2 { // ...
   void main() throws InterruptedException {
     CountDownLatch doneSignal = new CountDownLatch(N);
     Executor e = ...

     for (int i = 0; i < N; ++i) // create and start threads
       e.execute(new WorkerRunnable(doneSignal, i));

     doneSignal.await();           // wait for all to finish
   }
 }

 class WorkerRunnable implements Runnable {
   private final CountDownLatch doneSignal;
   private final int i;
   WorkerRunnable(CountDownLatch doneSignal, int i) {
      this.doneSignal = doneSignal;
      this.i = i;
   }
   public void run() {
      try {
        doWork(i);
        doneSignal.countDown();
      } catch (InterruptedException ex) {} // return;
   }

   void doWork() { ... }
 }

下面的內容是 轉載自並發編程網 – ifeve.com,文章地址:

並發工具類(一)等待多線程完成的CountDownLatch

應用場景

??假如有這樣一個需求,當我們需要解析一個Excel裏多個sheet的數據時,可以考慮使用多線程,每個線程解析一個sheet裏的數據,等到所有的sheet都解析完之後,程序需要提示解析完成。在這個需求中,要實現主線程等待所有線程完成sheet的解析操作,最簡單的做法是使用join。代碼如下:

public class JoinCountDownLatchTest {

    public static void main(String[] args) throws InterruptedException {
        Thread parser1 = new Thread(new Runnable() {
            @Override
            public void run() {
            }
        });

        Thread parser2 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("parser2 finish");
            }
        });

        parser1.start();
        parser2.start();
        parser1.join();
        parser2.join();
        System.out.println("all parser finish");
    }

}

??join用於讓當前執行線程等待join線程執行結束。其實現原理是不停檢查join線程是否存活,如果join線程存活則讓當前線程永遠wait,代碼片段如下,wait(0)表示永遠等待下去。

while (isAlive()) {
 wait(0);
}

??直到join線程中止後,線程的this.notifyAll會被調用,調用notifyAll是在JVM裏實現的,所以JDK裏看不到,有興趣的同學可以看看JVM源碼。JDK不推薦在線程實例上使用wait,notify和notifyAll方法。


??而在JDK1.5之後的並發包中提供的CountDownLatch也可以實現join的這個功能,並且比join的功能更多。

<pre>public class CountDownLatchTest {

    static CountDownLatch c = new CountDownLatch(2);

    public static void main(String[] args) throws InterruptedException {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(1);
                c.countDown();
                System.out.println(2);
                c.countDown();
            }
        }).start();

        c.await();
        System.out.println("3");
    }

}

CountDownLatch的構造函數接收一個int類型的參數作為計數器,如果你想等待N個點完成,這裏就傳入N。

當我們調用一次CountDownLatch的countDown方法時,N就會減1,CountDownLatch的await會阻塞當前線程,直到N變成零。由於countDown方法可以用在任何地方,所以這裏說的N個點,可以是N個線程,也可以是1個線程裏的N個執行步驟。用在多個線程時,你只需要把這個CountDownLatch的引用傳遞到線程裏。

其他方法:

如果有某個解析sheet的線程處理的比較慢,我們不可能讓主線程一直等待,所以我們可以使用另外一個帶指定時間的await方法,await(long time, TimeUnit unit): 這個方法等待特定時間後,就會不再阻塞當前線程。join也有類似的方法。

註意:

  • 計數器必須大於等於0,只是等於0時候,計數器就是零,調用await方法時不會阻塞當前線程。CountDownLatch不可能重新初始化或者修改CountDownLatch對象的內部計數器的值。
  • 一個線程調用countDown方法 happen-before 另外一個線程調用await方法。

並發工具類(一)等待多線程的CountDownLatch