前言
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() | 返回標識此鎖存器及其狀態的字串。 |
下面是API文件介紹的兩個經典用法:
:
Driver類中建立了一組worker 執行緒,所有的worker執行緒必須等待Driver類完成初始化動作,才能往下執行。完成初始化動作後,Driver類也必須等待所有worker執行緒完成才能結束。本例子中使用了兩個CountDownLatch
類:
- startSignal是一個啟動訊號,在 driver 為繼續執行 worker 做好準備之前,它會阻止所有的 worker 繼續執行。
- doneSignal是一個完成訊號,它允許 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() { ... }
}
複製程式碼
:另一種典型用法是,將一個問題分成 N 個部分(N個小任務),然後將這些任務Runnable
交由執行緒池來完成,每個子任務執行完成,就計數一次,主執行緒則等待這些子任務完成。當所有的子部分完成後,主執行緒就能夠通過 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() { ... }
}
複製程式碼
應用場景
假如有這樣一個需求,當我們需要解析一個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之後的併發包中提供的
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變成零。
。用在多個執行緒時,你只需要把這個CountDownLatch的引用傳遞到執行緒裡。
其他方法:
如果有某個解析sheet的執行緒處理的比較慢,我們不可能讓主執行緒一直等待,所以我們可以使用另外一個帶指定時間的await方法,await(long time, TimeUnit unit): 這個方法等待特定時間後,就會不再阻塞當前執行緒。join也有類似的方法。
- 計數器必須大於等於0,只是等於0時候,計數器就是零,呼叫await方法時不會阻塞當前執行緒。CountDownLatch不可能重新初始化或者修改CountDownLatch物件的內部計數器的值。
- 一個執行緒呼叫countDown方法 happen-before 另外一個執行緒呼叫await方法。
CountDownLatch 的原始碼分析
最後,我們簡單看一下 CountDownLatch是怎麼實現的:
public class CountDownLatch {
private final Sync sync;
public CountDownLatch(int count) {//構造器
//count少於0將丟擲異常
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public void countDown() {
sync.releaseShared(1);
}
//........
}
複製程式碼
在建立countDownLatch
,其構造器裡面建立了一個sync
類,並且await()
、countDown
方法都是都是通過此類來實現的。
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;
Sync(int count) {
//設定state的值為countDownLatch的計數的數目
setState(count);
}
int getCount() {
return getState();
}
//如果state值為0.也就是計數完成了,就不可以再獲取共享鎖,這也是為什麼CountLatch只能用一次
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
//是否可以釋放共享鎖
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1; //狀態state減一
if (compareAndSetState(c, nextc))
return nextc == 0;//計數到0了,表示釋放鎖成功。
}
}
}
複製程式碼
與大部分的併發工具類一樣,都是繼承使用了JDK提供的強大的AQS框架類AbstractQueuedSynchronizer
,而且使用的還是共享鎖,共享鎖能允許執行緒進入的執行緒數目,就是CountDownLatch
傳入的引數。
文章源地址:https://www.cnblogs.com/jinggod/p/8492067.html