基本概念
CountDownLatch 這個類能夠使一個或多個線程等待其他線程完成各自的工作後再執行。
例如,應用程序的主線程希望在負責啓動框架服務的線程已經啓動所有的框架服務之後再執行。
調用方法
假設有工人、老闆兩種角色。工人負責工作,老闆負責檢查工人已完成的工作。那麼存在以下條件:老闆必須等待工人完成工作後才能進行檢查。
- 工人
public class Worker implements Runnable {
private CountDownLatch cdl;
private String name;
public Worker(CountDownLatch cdl, String name) {
this.cdl = cdl;
this.name = name;
}
@Override
public void run() {
try {
System.out.println(name + " 開始工作 ...");
Thread.sleep(1000);
System.out.println(name + " 結束工作...");
cdl.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
- 老闆
// 老闆類
class Boss implements Runnable {
private CountDownLatch cdl;
private String name;
public Boss(CountDownLatch cdl, String name) {
this.cdl = cdl;
this.name = name;
}
@Override
public void run() {
try {
cdl.await();
System.out.println(name + " 檢查工作 ...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
- 調用過程
public class Test {
public static void main(String[] args) {
final CountDownLatch cdl = new CountDownLatch(2);
new Thread(new Boss(cdl, "大老闆")).start();
new Thread(new Worker(cdl, "工人甲")).start();
new Thread(new Worker(cdl, "工人乙")).start();
}
}
- 輸出結果
工人甲 開始工作 ...
工人乙 開始工作 ...
工人甲 結束工作...
工人乙 結束工作...
大老闆 檢查工作 ...
內部構造
- 構造函數,CountDownLatch 在構建時需要指定計數值(count)。
// 內部類
private final Sync sync;
public CountDownLatch(int count) {
if (count < 0) {
// 拋出異常...
}
this.sync = new Sync(count);
}
- 同步器,CountDownLatch 中定義了一個繼承自 AQS 的 Sync,並用 AQS 的狀態值表示計數值。
Sync(int count) {
setState(count);
}
- 計數值,即 count,類似於 ReetrantLock 的重入計數。只有在 count 爲 0 時,才能觸發釋放鎖的操作。
countDown
遞減操作,調用該方法的線程會遞減 CountDownLatch 的計數值。
// 遞減鎖的計數(若計數到達零,則釋放所有等待的線程)
public void countDown() {
sync.releaseShared(1);
}
調用過程如下:
AQS.releaseShared->CountDownLatch.tryReleaseShared->AQS.doReleaseShared
重點來看 tryReleaseShared 方法,該方法在 sync 作具體實現:
public boolean tryReleaseShared(int releases) {
for (;;) {
// 校驗計數值
int c = getState();
if (c == 0) {
return false;
}
// 遞減計數值,若計數爲 0,則喚醒所有通過 await 操作加入同步等待隊列的線程
int nextc = c - 1;
if (compareAndSetState(c, nextc)) {
return nextc == 0;
}
}
}
await
等待操作,調用該方法的線程會進入阻塞狀態,直到 CountDownLatch 的計數值爲 0 時,該線程纔會被喚醒。
public void await() throws InterruptedException {
// 獲取共享鎖,並且不忽略
sync.acquireSharedInterruptibly(1);
}
調用過程如下:
AQS.acquireSharedInterruptibly ->CountDownLatch.tryAcquireShared ->AQS.doAcquireSharedInterruptibly
重點來看 tryAcquireShared 方法,該方法在 sync 作具體實現:
// Sync
public int tryAcquireShared(int acquires) {
// count 爲 0 表示成功獲取鎖
return getState() == 0 ? 1 : -1;
}
總結
CountDownLatch 內部採用了共享鎖來實現。
- count :計數值可以理解爲通過鎖的重入計數。
- countDown :表示釋放鎖,每次操作都會遞減鎖的重入計數,只有重入計數爲 0 時,才能成功釋放鎖,否則返回 false。
- await :表示獲取鎖,只有在重入計數爲 0 時,纔可以成功獲取鎖,否則進入阻塞狀態。