CountDownLatch
是 Java 併發包(java.util.concurrent
)中的一個同步輔助類,它允許一個或多個執行緒等待一組操作完成。
一、設計理念
CountDownLatch
是基於 AQS(AbstractQueuedSynchronizer)實現的。其核心思想是維護一個倒計數,每次倒計數減少到零時,等待的執行緒才會繼續執行。它的主要設計目標是允許多個執行緒協調完成一組任務。
1. 建構函式與計數器
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
構造 CountDownLatch
時傳入的 count
決定了計數器的初始值。該計數器控制了執行緒的釋放。
2. AQS 支援的核心操作
AQS 是 CountDownLatch
的基礎,透過自定義內部類 Sync
實現,Sync
繼承了 AQS 並提供了必要的方法。以下是關鍵操作:
acquireShared(int arg)
: 如果計數器值為零,表示所有任務已完成,執行緒將獲得許可。releaseShared(int arg)
: 每次呼叫countDown()
,會減少計數器,當計數器降到零時,AQS 將釋放所有等待的執行緒。
3. 實現細節
countDown()
:呼叫releaseShared()
減少計數器,並通知等待執行緒。await()
:呼叫acquireSharedInterruptibly(1)
,如果計數器非零則阻塞等待。
二、底層原理
CountDownLatch
的核心是基於 AbstractQueuedSynchronizer
(AQS)來管理計數器狀態的。AQS 是 JUC 中許多同步工具的基礎,透過一個獨佔/共享模式的同步佇列實現執行緒的管理和排程。CountDownLatch
採用 AQS 的共享鎖機制來控制多個執行緒等待一個條件。
1. AQS 的共享模式
AQS 設計了兩種同步模式:獨佔模式(exclusive)和共享模式(shared)。CountDownLatch
使用共享模式:
- 獨佔模式:每次只能一個執行緒持有鎖,如
ReentrantLock
。 - 共享模式:允許多個執行緒共享鎖狀態,如
Semaphore
和CountDownLatch
。
CountDownLatch
的 await()
和 countDown()
方法對應於 AQS 的 acquireShared()
和 releaseShared()
操作。acquireShared()
會檢查同步狀態(計數器值),若狀態為零則立即返回,否則阻塞當前執行緒,進入等待佇列。releaseShared()
用於減少計數器並喚醒所有等待執行緒。
2. Sync 內部類的設計
CountDownLatch
透過一個私有的內部類 Sync
來實現同步邏輯。Sync
繼承自 AQS
,並重寫 tryAcquireShared(int arg)
和 tryReleaseShared(int arg)
方法。
static final class Sync extends AbstractQueuedSynchronizer {
Sync(int count) {
setState(count);
}
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
protected boolean tryReleaseShared(int releases) {
// 自旋減計數器
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c - 1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}
- tryAcquireShared(int):當計數器為零時返回 1(成功獲取鎖),否則返回 -1(阻塞)。
- tryReleaseShared(int):每次
countDown()
減少計數器值,當計數器到達零時返回true
,喚醒所有阻塞執行緒。
3. CAS 操作確保執行緒安全
tryReleaseShared
方法使用 CAS(compare-and-set)更新計數器,避免了鎖的開銷。CAS 操作由 CPU 原語(如 cmpxchg
指令)支援,實現了高效的非阻塞操作。這種設計保證了 countDown()
的執行緒安全性,使得多個執行緒能夠併發地減少計數器。
4. 內部的 ConditionObject
CountDownLatch
不支援複用,因為 AQS 的 ConditionObject
被設計為單一觸發模式。計數器一旦降至零,CountDownLatch
無法重置,只能釋放所有執行緒,而不能再次設定初始計數器值。這就是其不可複用的根本原因。
三、應用場景
- 等待多執行緒任務完成:
CountDownLatch
常用於需要等待一組執行緒完成其任務後再繼續的場景,如批處理任務。 - 並行執行再彙總:在某些資料分析或計算密集型任務中,將任務分割成多個子任務並行執行,主執行緒等待所有子任務完成後再彙總結果。
- 多服務依賴協調:當一個服務依賴多個其他服務時,可以使用
CountDownLatch
來同步各個服務的呼叫,並確保所有依賴服務準備好之後再執行主任務。
四、示例程式碼
以下示例展示如何使用 CountDownLatch
實現一個併發任務等待所有子任務完成的機制。
import java.util.concurrent.CountDownLatch;
public class CountDownLatchExample {
private static final int TASK_COUNT = 5;
private static CountDownLatch latch = new CountDownLatch(TASK_COUNT);
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < TASK_COUNT; i++) {
new Thread(new Task(i + 1, latch)).start();
}
// 主執行緒等待所有任務完成
latch.await();
System.out.println("所有任務已完成,繼續主執行緒任務");
}
static class Task implements Runnable {
private final int taskNumber;
private final CountDownLatch latch;
Task(int taskNumber, CountDownLatch latch) {
this.taskNumber = taskNumber;
this.latch = latch;
}
@Override
public void run() {
try {
System.out.println("子任務 " + taskNumber + " 開始執行");
Thread.sleep((int) (Math.random() * 1000)); // 模擬任務執行時間
System.out.println("子任務 " + taskNumber + " 完成");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
latch.countDown(); // 完成一個任務,計數器減一
}
}
}
}
五、與其他同步工具的對比
1. CyclicBarrier
原理和用途:
CyclicBarrier
也允許一組執行緒相互等待,直到所有執行緒到達屏障位置(barrier point)。- 它適合用於多階段任務或分階段匯聚,如處理分塊計算時每階段彙總結果。
底層實現:
CyclicBarrier
內部透過ReentrantLock
和Condition
實現,屏障次數可以重置,從而支援迴圈使用。
與 CountDownLatch 的對比:
CyclicBarrier
的可複用性使其適合重複的同步場景,而CountDownLatch
是一次性的。CountDownLatch
更靈活,允許任意執行緒呼叫countDown()
,適合分散式任務。CyclicBarrier
需要指定的執行緒達到屏障。
2. Semaphore
原理和用途:
Semaphore
主要用於控制資源訪問的併發數量,如限制資料庫連線池的訪問。
底層實現:
Semaphore
基於 AQS 的共享模式實現,類似於CountDownLatch
,但允許透過指定的“許可證”數量控制資源。
與 CountDownLatch 的對比:
Semaphore
可以動態增加/減少許可,而CountDownLatch
只能遞減。Semaphore
適合控制訪問限制,而CountDownLatch
用於同步點倒計數。
3. Phaser
原理和用途:
Phaser
是CyclicBarrier
的增強版,允許動態調整參與執行緒的數量。- 適合多階段任務同步,並能隨時增加或減少參與執行緒。
底層實現:
Phaser
內部包含一個計數器,用於管理當前階段的參與執行緒,允許任務動態註冊或登出。
與 CountDownLatch 的對比:
Phaser
更適合複雜場景,能夠靈活控制階段和參與執行緒;CountDownLatch
的結構簡單,只能用於一次性同步。Phaser
的設計更復雜,適合長時間、多執行緒協調任務,而CountDownLatch
更適合簡單任務等待。
4、總結
CountDownLatch
是一個輕量級、不可複用的倒計數同步器,適合簡單的一次性執行緒協調。其基於 AQS 的共享鎖實現使得執行緒等待和計數器更新具有高效的併發性。雖然 CountDownLatch
不具備重用性,但其設計簡潔,尤其適合需要等待多執行緒任務完成的場景。
與其他 JUC 工具相比:
CyclicBarrier
更適合多階段同步、階段性彙總任務。Semaphore
適合資源訪問控制,具有可控的許可量。Phaser
靈活性更高,適合動態參與執行緒、複雜多階段任務。
選擇適合的同步工具,取決於任務的性質、執行緒參與動態性以及是否需要重用同步控制。