CountDownLatch 并發編程中的同步利器
文章目錄
- CountDownLatch 并發編程中的同步利器
- 一、`CountDownLatch` 基礎概念
- 1.1 什么是 `CountDownLatch`?
- 1.2 `CountDownLatch` 的核心方法
- 1.3 基本使用示例
- 二、`CountDownLatch` 實戰應用
- 2.1 應用場景一:并行任務協調
- 2.2 應用場景二:多階段并發處理
- 2.3 應用場景三:并發測試
- 三、源碼解析
- 3.1 `CountDownLatch` 的構造與字段
- 3.2 核心方法實現
- 3.2.1 await 方法
- 3.2.2 countDown 方法
- 3.3 工作流程圖解
- 四、`CountDownLatch` 的高級用法
- 4.1 帶超時的等待
- 4.2 結合 CyclicBarrier 實現復雜同步
- 4.3 注意事項與最佳實踐
- 五、`CountDownLatch` 與其他同步工具的對比
- 5.1 `CountDownLatch` vs CyclicBarrier
- 5.2 `CountDownLatch` vs Semaphore
- 5.3 `CountDownLatch` vs Join
- 六、面試中的 `CountDownLatch` 問題解析
- 6.1 基礎概念題
- 6.2 代碼實現題
- 6.3 原理分析題
?

? 在多線程并發編程中,線程同步是一個永恒的話題。當我們需要等待多個線程完成某些操作后再繼續執行,或者需要等待某個條件滿足后才能開始執行,CountDownLatch
便成為了一把利器。它就像一個倒計時器,只有當計數歸零時,等待的線程才能繼續執行。
一、CountDownLatch
基礎概念
1.1 什么是 CountDownLatch
?
CountDownLatch
是 Java 并發包(java.util.concurrent)中的一個同步工具類,它允許一個或多個線程等待一系列指定操作的完成。CountDownLatch
的核心思想是維護一個計數器,每完成一個操作,計數器減一,當計數器歸零時,等待的線程被釋放繼續執行。
這聽起來有點抽象,我們可以通過一個簡單的比喻來理解:想象你是一個項目經理,手下有5個開發人員,每人負責一個模塊。只有當這5個模塊都開發完成后,整個項目才能進入測試階段。CountDownLatch
就相當于一個跟蹤這5個模塊開發進度的工具,每完成一個模塊,計數器減一,當計數器變為0時,測試團隊就可以開始工作了。
1.2 CountDownLatch
的核心方法
CountDownLatch
主要有兩個核心方法:
- countDown():遞減計數器,表示一個操作已完成
- await():等待計數器歸零,如果計數器大于0,則當前線程進入等待狀態,此外還有一些其他實用方法:
- await(long timeout, TimeUnit unit):等待計數器歸零,但最多等待指定的時間
- getCount():獲取當前計數器的值
1.3 基本使用示例
下面是一個簡單的示例,演示 CountDownLatch
的基本使用:
import java.util.concurrent.CountDownLatch;public class CountDownLatchDemo {public static void main(String[] args) throws InterruptedException {// 創建一個計數器為5的CountDownLatchCountDownLatch latch = new CountDownLatch(5);System.out.println("主線程開始等待所有工作線程完成...");// 創建并啟動5個工作線程for (int i = 0; i < 5; i++) {final int workerId = i + 1;new Thread(() -> {try {// 模擬工作線程執行任務System.out.println("工作線程" + workerId + "開始執行任務");Thread.sleep((long) (Math.random() * 1000));System.out.println("工作線程" + workerId + "完成任務");// 任務完成,計數器減一latch.countDown();} catch (InterruptedException e) {Thread.currentThread().interrupt();}}).start();}// 主線程等待所有工作線程完成latch.await();System.out.println("所有工作線程已完成,主線程繼續執行");}
}
在這個例子中,主線程創建了5個工作線程,然后調用latch.await()
等待所有工作線程完成。每個工作線程完成任務后調用latch.countDown()
將計數器減一。當所有5個工作線程都完成任務后,計數器歸零,主線程從 await() 方法返回并繼續執行。
二、CountDownLatch
實戰應用
2.1 應用場景一:并行任務協調
CountDownLatch
最常見的應用場景是協調并行任務的執行。當一個復雜任務可以分解為多個獨立的子任務并行執行時,我們可以使用 CountDownLatch
來等待所有子任務完成后再進行下一步操作。
下面是一個模擬并行加載系統模塊的例子:
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class SystemInitializer {public static void main(String[] args) throws InterruptedException {// 需要初始化的模塊列表List<String> modules = Arrays.asList("數據庫連接模塊", "緩存服務模塊", "消息隊列模塊", "用戶認證模塊", "日志服務模塊");int moduleCount = modules.size();// 創建與模塊數量相同的計數器CountDownLatch latch = new CountDownLatch(moduleCount);System.out.println("系統啟動中,等待所有模塊初始化...");// 創建固定大小的線程池ExecutorService executor = Executors.newFixedThreadPool(Math.min(moduleCount, 3));// 啟動時間long startTime = System.currentTimeMillis();// 為每個模塊創建初始化任務for (String module : modules) {executor.submit(() -> {try {initModule(module);} finally {// 模塊初始化完成,計數器減一latch.countDown();System.out.println("模塊 [" + module + "] 初始化完成,還剩 " + latch.getCount() + " 個模塊");}});}// 等待所有模塊初始化完成latch.await();// 計算總耗時long endTime = System.currentTimeMillis();System.out.println("所有模塊初始化完成,系統啟動成功!總耗時: " + (endTime - startTime) + "ms");// 關閉線程池executor.shutdown();}private static void initModule(String moduleName) {System.out.println("開始初始化模塊: " + moduleName);try {// 模擬模塊初始化耗時long sleepTime = (long) (Math.random() * 2000 + 1000);Thread.sleep(sleepTime);System.out.println("模塊 [" + moduleName + "] 初始化耗時: " + sleepTime + "ms");} catch (InterruptedException e) {Thread.currentThread().interrupt();System.out.println("模塊 [" + moduleName + "] 初始化被中斷");}}
}
在這個例子中,我們使用 CountDownLatch
來協調系統各個模塊的初始化過程。系統啟動時需要初始化多個模塊,每個模塊的初始化可以并行進行,但只有當所有模塊都初始化完成后,系統才能正常運行。
2.2 應用場景二:多階段并發處理
有時我們需要將一個大型任務分為多個階段,每個階段都需要多個線程協同完成,且下一階段必須等待上一階段完全完成后才能開始。這種情況下,我們可以為每個階段創建一個 CountDownLatch
。
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class MultiPhaseTask {public static void main(String[] args) throws InterruptedException {int threadCount = 3;// 創建線程池ExecutorService executor = Executors.newFixedThreadPool(threadCount);// 第一階段的計數器CountDownLatch phase1Latch = new CountDownLatch(threadCount);// 第二階段的計數器CountDownLatch phase2Latch = new CountDownLatch(threadCount);// 第三階段的計數器CountDownLatch phase3Latch = new CountDownLatch(threadCount);System.out.println("開始執行多階段任務...");// 啟動所有工作線程for (int i = 0; i < threadCount; i++) {final int workerId = i + 1;executor.submit(() -> {try {// 第一階段:數據準備System.out.println("工作線程" + workerId + "開始第一階段:數據準備");Thread.sleep((long) (Math.random() * 1000));System.out.println("工作線程" + workerId + "完成第一階段");phase1Latch.countDown();// 等待所有線程完成第一階段phase1Latch.await();System.out.println("所有線程完成第一階段,工作線程" + workerId + "開始第二階段");// 第二階段:數據處理Thread.sleep((long) (Math.random() * 1500));System.out.println("工作線程" + workerId + "完成第二階段");phase2Latch.countDown();// 等待所有線程完成第二階段phase2Latch.await();System.out.println("所有線程完成第二階段,工作線程" + workerId + "開始第三階段");// 第三階段:結果匯總Thread.sleep((long) (Math.random() * 800));System.out.println("工作線程" + workerId + "完成第三階段");phase3Latch.countDown();} catch (InterruptedException e) {Thread.currentThread().interrupt();}});}// 等待所有階段完成phase3Latch.await();System.out.println("所有階段都已完成,任務結束");// 關閉線程池executor.shutdown();}
}
在這個例子中,我們將任務分為三個階段:數據準備、數據處理和結果匯總。每個階段都需要多個線程共同完成,且只有當所有線程都完成當前階段后,才能進入下一階段。
2.3 應用場景三:并發測試
CountDownLatch
在性能測試中也有重要應用,特別是需要模擬大量并發請求的場景。下面是一個模擬并發請求的例子:
import java.util.concurrent.CountDownLatch;startLatch.await();// 記錄請求開始時間long startTime = System.currentTimeMillis();System.out.println("請求" + requestId + "開始執行");// 模擬發送HTTP請求boolean success = sendHttpRequest(requestId);// 記錄請求結束時間并計算耗時long endTime = System.currentTimeMillis();long duration = endTime - startTime;// 根據請求結果更新計數器if (success) {successCount.incrementAndGet();System.out.println("請求" + requestId + "成功,耗時: " + duration + "ms");} else {failureCount.incrementAndGet();System.out.println("請求" + requestId + "失敗,耗時: " + duration + "ms");}} catch (InterruptedException e) {Thread.currentThread().interrupt();} finally {// 請求完成,計數器減一endLatch.countDown();}});}// 記錄測試開始時間long testStartTime = System.currentTimeMillis();// 發出開始信號,所有線程同時開始執行System.out.println("所有請求準備就緒,開始并發測試...");startLatch.countDown();// 等待所有請求完成boolean allCompleted = endLatch.await(30, TimeUnit.SECONDS);// 記錄測試結束時間long testEndTime = System.currentTimeMillis();long totalDuration = testEndTime - testStartTime;// 輸出測試結果System.out.println("\n并發測試完成!");if (!allCompleted) {System.out.println("警告:有些請求在超時時間內未完成");}System.out.println("總耗時: " + totalDuration + "ms");System.out.println("成功請求數: " + successCount.get());System.out.println("失敗請求數: " + failureCount.get());System.out.println("平均響應時間: " + (totalDuration / concurrentRequests) + "ms");// 關閉線程池executor.shutdown();}// 模擬發送HTTP請求private static boolean sendHttpRequest(int requestId) {try {// 模擬請求處理時間,隨機100-500msThread.sleep((long) (Math.random() * 400 + 100));// 模擬偶爾的請求失敗,約5%的失敗率return Math.random() > 0.05;} catch (InterruptedException e) {Thread.currentThread().interrupt();return false;}}
}
在這個例子中,我們使用兩個 CountDownLatch
:
startLatch
用于控制所有線程同時開始,模擬真實的并發請求場景。- endLatch 用于等待所有請求完成,以便統計整體測試結果。
這種方式可以準確測量系統在高并發情況下的性能表現,是性能測試的常用手段。
三、源碼解析
了解 CountDownLatch
的內部實現原理,有助于我們更好地使用它。CountDownLatch
的實現基于 AQS(AbstractQueuedSynchronizer)框架,這也是 Java 并發包中許多同步器的基礎。
3.1 CountDownLatch
的構造與字段
先看 CountDownLatch
的構造函數:
public CountDownLatch(int count) {if (count < 0) throw new IllegalArgumentException("count < 0");this.sync = new Sync(count);
}
CountDownLatch
內部維護了一個 Sync 實例,它是 AQS 的子類:
private static final class Sync extends AbstractQueuedSynchronizer {private static final long serialVersionUID = 4982264981922014374L;Sync(int count) {setState(count);}int getCount() {return getState();}protected int tryAcquireShared(int acquires) {return (getState() == 0) ? 1 : -1;}protected boolean tryReleaseShared(int releases) {// Decrement count; signal when transition to zerofor (;;) {int c = getState();if (c == 0)return false;int nextc = c-1;if (compareAndSetState(c, nextc))return nextc == 0;}}
}
Sync 類直接使用 AQS 的 state 變量作為計數器。構造函數中,將 state 設置為指定的計數值。
3.2 核心方法實現
3.2.1 await 方法
public void await() throws InterruptedException {sync.acquireSharedInterruptibly(1);
}
await()
方法調用 AQS 的 acquireSharedInterruptibly()
,這個方法會調用 Sync 中重寫的 tryAcquireShared()
方法。只有當計數器(state)為 0 時,tryAcquireShared()
才會返回正值,表示可以獲取共享鎖,否則線程將被阻塞。
3.2.2 countDown 方法
public void countDown() {sync.releaseShared(1);
}
countDown()
方法調用 AQS 的 releaseShared()
,這個方法會調用 Sync 中重寫的 tryReleaseShared()
方法。tryReleaseShared()
通過 CAS
操作減少計數器的值,當計數器變為 0 時,會返回 true,此時 AQS 會釋放所有等待的線程。
3.3 工作流程圖解
CountDownLatch
的工作流程可以簡化為以下幾個步驟:
-
創建
CountDownLatch
對象,初始化計數器 -
等待線程調用 await() 方法,如果計數器大于 0,線程將被阻塞
-
工作線程完成任務后調用
countDown()
方法,計數器減一 -
當計數器減至 0 時,
AQS
會釋放所有等待的線程,它們從await()
方法返回繼續執行
四、CountDownLatch
的高級用法
4.1 帶超時的等待
有時我們不希望無限期地等待所有操作完成,這時可以使用帶超時參數的 await()
方法:
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;public class CountDownLatchWithTimeout {public static void main(String[] args) {// 創建計數器為3的CountDownLatchCountDownLatch latch = new CountDownLatch(3);System.out.println("主線程開始等待工作線程完成...");// 啟動工作線程for (int i = 0; i < 3; i++) {final int workerId = i + 1;new Thread(() -> {try {System.out.println("工作線程" + workerId + "開始執行");// 模擬工作線程耗時,第三個線程會故意執行很長時間long sleepTime = (workerId == 3) ? 5000 : 1000;Thread.sleep(sleepTime);System.out.println("工作線程" + workerId + "完成執行");latch.countDown();} catch (InterruptedException e) {Thread.currentThread().interrupt();}}).start();}try {// 等待,但最多等待2秒boolean completed = latch.await(2, TimeUnit.SECONDS);if (completed) {System.out.println("所有工作線程在超時前完成了任務");} else {System.out.println("等待超時,但仍有" + latch.getCount() + "個任務未完成");System.out.println("主線程將繼續執行,不再等待未完成的任務");}} catch (InterruptedException e) {Thread.currentThread().interrupt();System.out.println("主線程等待被中斷");}System.out.println("主線程繼續執行其他操作");}
}
在這個例子中,我們給 await()
方法設置了2秒的超時時間。由于第三個工作線程需要5秒才能完成,因此主線程會在等待2秒后繼續執行,即使并非所有工作線程都已完成。
4.2 結合 CyclicBarrier 實現復雜同步
在更復雜的場景中,我們可能需要結合使用 CountDownLatch
和 CyclicBarrier 來實現多階段、多方向的同步。例如,在一個分布式計算任務中,既需要等待所有工作節點準備就緒后才開始計算,又需要等待所有計算結果返回后才能進行匯總。
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class ComplexSynchronization {public static void main(String[] args) throws InterruptedException {int workerCount = 5;// 用于等待所有工作線程完成初始化CountDownLatch initLatch = new CountDownLatch(workerCount);// 用于等待所有工作線程完成計算CountDownLatch computeLatch = new CountDownLatch(workerCount);// 用于同步所有工作線程開始計算的時刻CyclicBarrier computeBarrier = new CyclicBarrier(workerCount, () -> {System.out.println("所有工作線程已就緒,開始并行計算");});ExecutorService executor = Executors.newFixedThreadPool(workerCount);System.out.println("主線程開始初始化工作線程...");for (int i = 0; i < workerCount; i++) {final int workerId = i + 1;executor.submit(() -> {try {// 初始化階段System.out.println("工作線程" + workerId + "開始初始化");Thread.sleep((long) (Math.random() * 1000));System.out.println("工作線程" + workerId + "初始化完成");// 初始化完成,計數器減一initLatch.countDown();// 等待所有線程初始化完成,并同步開始計算computeBarrier.await();// 計算階段System.out.println("工作線程" + workerId + "開始計算");Thread.sleep((long) (Math.random() * 2000));System.out.println("工作線程" + workerId + "計算完成");// 計算完成,計數器減一computeLatch.countDown();} catch (Exception e) {e.printStackTrace();}});}// 等待所有工作線程初始化完成System.out.println("主線程等待所有工作線程初始化完成...");initLatch.await();System.out.println("所有工作線程初始化完成,等待計算結果...");// 等待所有工作線程計算完成computeLatch.await();System.out.println("所有工作線程計算完成,開始匯總結果");// 模擬結果匯總Thread.sleep(500);System.out.println("結果匯總完成,任務結束");executor.shutdown();}
}
在這個例子中,我們使用了:
- initLatch(
CountDownLatch
)等待所有工作線程完成初始化。 - computeBarrier(CyclicBarrier)同步所有工作線程開始計算的時刻。
- computeLatch(
CountDownLatch
)等待所有工作線程完成計算。
這種組合使用可以實現更復雜的多階段同步場景。
4.3 注意事項與最佳實踐
在使用 CountDownLatch
時,有一些注意事項和最佳實踐:
-
計數器不可重置:
CountDownLatch
的計數器一旦歸零就不能重新設置,如果需要重復使用,應考慮CyclicBarrier
。 -
防止計數器錯誤:確保
countDown()
方法被正確調用,特別是在有異常的情況下,通常應該在finally
塊中調用。 -
避免死鎖:如果某些線程未能調用
countDown()
,等待的線程將永遠阻塞,應考慮使用帶超時的await()
方法。 -
資源釋放:在所有線程都完成后,及時釋放資源,如關閉線程池。
-
與線程池結合:通常應將
CountDownLatch
與線程池結合使用,而不是直接創建大量線程。
// 錯誤的使用方式(沒有在finally中調用countDown)
public void wrongWay() {CountDownLatch latch = new CountDownLatch(1);executor.submit(() -> {try {// 如果這里拋出異常,countDown不會被調用doSomething();latch.countDown();} catch (Exception e) {e.printStackTrace();}});
}// 正確的使用方式
public void rightWay() {CountDownLatch latch = new CountDownLatch(1);executor.submit(() -> {try {doSomething();} catch (Exception e) {e.printStackTrace();} finally {// 確保即使出現異常,countDown也會被調用latch.countDown();}});
}
五、CountDownLatch
與其他同步工具的對比
為了更全面地理解 CountDownLatch
,我們將它與其他常見的同步工具進行對比:
5.1 CountDownLatch
vs CyclicBarrier
- 重用性:
CountDownLatch
的計數器減到0后就不能再用,而 CyclicBarrier 可以通過 reset() 方法重置,可以重復使用。 - 觸發機制:
CountDownLatch
是一個線程等待多個線程,或多個線程等待一個線程;CyclicBarrier 是多個線程互相等待,直到所有線程都到達屏障點。 - 計數方式:
CountDownLatch
的計數器只能減少,CyclicBarrier 的計數器可以減少也可以重置。
5.2 CountDownLatch
vs Semaphore
- 作用范圍:
CountDownLatch
主要用于等待事件;Semaphore 用于控制對資源的并發訪問數量。 - 計數方向:
CountDownLatch
計數器只能遞減直至歸零;Semaphore 的計數器可以遞增遞減,只要不超過設定的最大值。 - 使用場景:
CountDownLatch
適用于一次性等待場景;Semaphore 適用于限制并發訪問資源的場景。
5.3 CountDownLatch
vs Join
- 靈活性:
CountDownLatch
可以在任何時候調用 countDown(),而不必等待線程結束;Thread.join() 必須等待線程執行完成。 - 粒度:
CountDownLatch
可以實現更細粒度的控制,一個線程可以多次 countDown();join() 只能等待整個線程執行完畢。 - 中斷處理:兩者都支持中斷,但
CountDownLatch
可以設置等待超時。
下面是一個簡單的對比示例:
import java.util.concurrent.*;}private static void semaphoreExample() throws InterruptedException {// 創建只允許3個線程同時訪問的信號量Semaphore semaphore = new Semaphore(3);ExecutorService executor = Executors.newFixedThreadPool(10);for (int i = 0; i < 10; i++) {final int id = i;executor.submit(() -> {try {System.out.println("線程" + id + "等待獲取許可");semaphore.acquire();System.out.println("線程" + id + "獲得許可,開始執行");Thread.sleep(1000);System.out.println("線程" + id + "執行完畢,釋放許可");semaphore.release();} catch (InterruptedException e) {Thread.currentThread().interrupt();}});}// 給足夠時間讓任務執行Thread.sleep(5000);executor.shutdown();}private static void joinExample() throws InterruptedException {Thread[] threads = new Thread[3];for (int i = 0; i < threads.length; i++) {final int id = i;threads[i] = new Thread(() -> {try {System.out.println("線程" + id + "開始執行");Thread.sleep(1000 + id * 500);System.out.println("線程" + id + "執行完畢");} catch (InterruptedException e) {Thread.currentThread().interrupt();}});threads[i].start();}System.out.println("等待所有線程完成...");for (Thread thread : threads) {thread.join();}System.out.println("所有線程已完成!");}
}
這個例子展示了CountDownLatch
、CyclicBarrier
、Semaphore
和Thread.join()
的不同使用方式和特點,有助于理解它們各自的應用場景。
六、面試中的 CountDownLatch
問題解析
CountDownLatch
是Java并發編程面試中的高頻話題。下面列出一些常見面試問題及其答案:
6.1 基礎概念題
問題1:什么是CountDownLatch
?它的主要用途是什么?
答:
CountDownLatch
是Java并發包中的同步工具類,允許一個或多個線程等待一系列指定操作的完成。它主要用于以下場景:
- 讓主線程等待子線程完成再繼續執行
- 實現多個線程之間的同步
- 控制并發測試中的并發量
- 多階段并發任務的協調
它通過維護一個計數器來工作,每完成一個操作就減一,當計數器歸零時釋放所有等待的線程。
問題2:CountDownLatch
與CyclicBarrier的區別?
答:主要區別有:
重用性:
CountDownLatch
是一次性的,計數器歸零后不能重置;CyclicBarrier可以通過reset()方法重置,可重復使用。計數方向:
CountDownLatch
計數器只能減少;CyclicBarrier的計數器是先減少,后自動重置。觸發方式:
CountDownLatch
是調用countDown()方法;CyclicBarrier是調用await()方法。使用場景:
CountDownLatch
適用于一個或多個線程等待其他操作完成;CyclicBarrier適用于多個線程互相等待至某個狀態,然后一起繼續運行。
6.2 代碼實現題
問題:實現一個高并發限流器,限制最大并發請求數為100,當請求處理完成后允許新的請求進入。
答:可以使用Semaphore實現最基本的限流功能,但結合
CountDownLatch
可以實現更加靈活的控制:
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicInteger;public class RequestLimiter {// 最大并發數限制private final Semaphore semaphore;// 已完成的請求計數private final AtomicInteger completedRequests = new AtomicInteger(0);// 用于等待所有請求處理完成private CountDownLatch completionLatch;public RequestLimiter(int maxConcurrent) {this.semaphore = new Semaphore(maxConcurrent);}// 開始一輪請求處理public void startBatch(int requestCount) {this.completionLatch = new CountDownLatch(requestCount);this.completedRequests.set(0);}// 處理請求public void processRequest(Runnable task) throws InterruptedException {// 獲取許可semaphore.acquire();try {// 執行請求處理task.run();} finally {// 釋放許可semaphore.release();// 完成一個請求completedRequests.incrementAndGet();// 計數器減一completionLatch.countDown();}}// 等待所有請求處理完成public void awaitCompletion() throws InterruptedException {completionLatch.await();}// 獲取已完成的請求數public int getCompletedRequestCount() {return completedRequests.get();}public static void main(String[] args) throws InterruptedException {RequestLimiter limiter = new RequestLimiter(10);int totalRequests = 100;// 開始處理100個請求limiter.startBatch(totalRequests);// 提交請求for (int i = 0; i < totalRequests; i++) {final int requestId = i;new Thread(() -> {try {System.out.println("請求 " + requestId + " 等待處理");limiter.processRequest(() -> {try {// 模擬請求處理System.out.println("處理請求 " + requestId);Thread.sleep((long) (Math.random() * 500));} catch (InterruptedException e) {Thread.currentThread().interrupt();}});System.out.println("請求 " + requestId + " 處理完成");} catch (InterruptedException e) {Thread.currentThread().interrupt();}}).start();}// 等待所有請求處理完成limiter.awaitCompletion();System.out.println("所有請求處理完成,共處理: " + limiter.getCompletedRequestCount() + " 個請求");}
}
6.3 原理分析題
問題:CountDownLatch
的內部實現原理是什么?它是如何實現線程等待和喚醒的?
答:
CountDownLatch
內部基于AQS(AbstractQueuedSynchronizer)
框架實現:
狀態管理:使用
AQS
的state變量作為計數器等待機制:調用await()時,如果計數器不為0,則當前線程會被加入到
AQS
的等待隊列中喚醒機制:調用
countDown()
時,計數器減1,當減至0時,AQS
會釋放所有等待的線程線程安全性:通過
CAS(Compare And Swap)
操作保證計數器更新的原子性具體實現上,
CountDownLatch
包含一個繼承自AQS
的內部類Sync
,它重寫了tryAcquireShared
和tryReleaseShared
方法:
- tryAcquireShared:只有當計數器為0時才返回1,表示獲取成功。
- tryReleaseShared:使用
CAS
操作安全地減少計數器,并在計數器變為0時返回true