並發工具類(一)等待多線程的CountDownLatch
前言
??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時,便是等待線程的運行時機,將會繼續往下運行。
方法名稱 | 描 述 |
---|---|
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