CountDownLatch 并發編程中的同步利器

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 的工作流程可以簡化為以下幾個步驟:

  1. 創建 CountDownLatch 對象,初始化計數器

  2. 等待線程調用 await() 方法,如果計數器大于 0,線程將被阻塞

  3. 工作線程完成任務后調用 countDown() 方法,計數器減一

  4. 當計數器減至 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 時,有一些注意事項和最佳實踐:

  1. 計數器不可重置CountDownLatch 的計數器一旦歸零就不能重新設置,如果需要重復使用,應考慮 CyclicBarrier

  2. 防止計數器錯誤:確保 countDown() 方法被正確調用,特別是在有異常的情況下,通常應該在finally塊中調用。

  3. 避免死鎖:如果某些線程未能調用 countDown(),等待的線程將永遠阻塞,應考慮使用帶超時的await()方法。

  4. 資源釋放:在所有線程都完成后,及時釋放資源,如關閉線程池。

  5. 與線程池結合:通常應將 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("所有線程已完成!");}
}

這個例子展示了CountDownLatchCyclicBarrierSemaphoreThread.join() 的不同使用方式和特點,有助于理解它們各自的應用場景。

六、面試中的 CountDownLatch 問題解析

CountDownLatch 是Java并發編程面試中的高頻話題。下面列出一些常見面試問題及其答案:

6.1 基礎概念題

問題1:什么是CountDownLatch?它的主要用途是什么?

答:CountDownLatch是Java并發包中的同步工具類,允許一個或多個線程等待一系列指定操作的完成。它主要用于以下場景:

  • 讓主線程等待子線程完成再繼續執行
  • 實現多個線程之間的同步
  • 控制并發測試中的并發量
  • 多階段并發任務的協調

它通過維護一個計數器來工作,每完成一個操作就減一,當計數器歸零時釋放所有等待的線程。

問題2:CountDownLatch與CyclicBarrier的區別?

答:主要區別有:

  1. 重用性:CountDownLatch是一次性的,計數器歸零后不能重置;CyclicBarrier可以通過reset()方法重置,可重復使用。

  2. 計數方向:CountDownLatch計數器只能減少;CyclicBarrier的計數器是先減少,后自動重置。

  3. 觸發方式:CountDownLatch是調用countDown()方法;CyclicBarrier是調用await()方法。

  4. 使用場景: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)框架實現:

  1. 狀態管理:使用AQS的state變量作為計數器

  2. 等待機制:調用await()時,如果計數器不為0,則當前線程會被加入到AQS的等待隊列中

  3. 喚醒機制:調用countDown()時,計數器減1,當減至0時,AQS會釋放所有等待的線程

  4. 線程安全性:通過CAS(Compare And Swap)操作保證計數器更新的原子性

具體實現上,CountDownLatch包含一個繼承自AQS的內部類Sync,它重寫了tryAcquireSharedtryReleaseShared方法:

  • tryAcquireShared:只有當計數器為0時才返回1,表示獲取成功。
  • tryReleaseShared:使用CAS操作安全地減少計數器,并在計數器變為0時返回true

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/79275.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/79275.shtml
英文地址,請注明出處:http://en.pswp.cn/web/79275.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Linux 內核鏈表宏的詳細解釋

&#x1f527; Linux 內核鏈表結構概覽 Linux 內核中的鏈表結構定義在頭文件 <linux/list.h> 中。核心結構是&#xff1a; struct list_head {struct list_head *next, *prev; }; 它表示一個雙向循環鏈表的節點。鏈表的所有操作都圍繞這個結構體展開。 &#x1f9e9; …

分書問題的遞歸枚舉算法

分數問題的遞歸枚舉算法 一、問題引入二、解題步驟1.問題分析思維導圖2.解題步驟 三、代碼實現1.代碼2.復雜度分析 四、個人總結 一、問題引入 分書問題是指&#xff1a;已知 n 個人對 m 本書的喜好&#xff08;n≤m&#xff09;&#xff0c;現要將 m 本書分給 n 個人&#xf…

密碼學--AES

一、實驗目的 1、完成AES算法中1輪加密和解密操作 2、掌握AES的4個基本處理步驟 3、理解對稱加密算法的“對稱”思想 二、實驗內容 1、題目內容描述 &#xff08;1&#xff09;利用C語言實現字節代換和逆向字節代換&#xff0c;字節查S盒代換 &#xff08;2&#xff09;利…

【工具記錄分享】提取bilibili視頻字幕

F12大法 教程很多 但方法比較統一 例快速提取視頻字幕&#xff01;適用B站、AI字幕等等。好用 - 嗶哩嗶哩 無腦小工具 嗶哩嗶哩B站字幕下載_在線字幕解析-飛魚視頻下載助手 把鏈接扔進去就會自動生成srt文件 需要txt可以配合&#xff1a; SRT轉為TXT

使用fdisk 、gdisk管理分區

用 fdisk 管理分區 fdisk 命令工具默認將磁盤劃分為 mbr 格式的分區 命令&#xff1a; fdisk 設備名 fdisk 命令以交互方式進行操作的&#xff0c;在菜單中選擇相應功能鍵即可 [rootlocalhost ~]# fdisk /dev/sda # 對 sda 進行分區 Command (m for help): # 進入 fdis…

【Linux基礎】程序和軟件安裝管理命令

目錄 install命令 which命令 install命令 作用&#xff1a;它是用于安裝或復制文件到指定位置&#xff0c;并且可以同時設置文件的權限、所有者和所屬組等屬性。它通常用于腳本中&#xff0c;用于自動化安裝程序或配置文件的部署。 基本用法&#xff1a; install [選項] 源…

C++模板梳理

目錄 函數模板 類模板 變量模板 模板全特化 模板偏特化 模板顯式實例化解決文件分離問題 折疊表達式 模板的二階段編譯 待決名(dependent name) SFINAE 概念與約束 函數模板 函數模板不是函數&#xff0c;只有實例化的函數模板&#xff0c;編譯器才能生成實際的函數…

數據鏈共享:從印巴空戰到工業控制的跨越性應用

摘要 本文通過對印巴空戰中數據鏈共享發揮關鍵作用的分析&#xff0c;引出數據鏈共享在工業控制領域同樣具有重大價值的觀點。深入闡述 DIOS 工業控制操作系統作為工業數據鏈共享基礎技術的特點、架構及應用優勢&#xff0c;對比空戰場景與工業控制場景下數據鏈共享的相…

巡檢機器人數據處理技術的創新與實踐

摘要 隨著科技的飛速發展&#xff0c;巡檢機器人在各行業中逐漸取代人工巡檢&#xff0c;展現出高效、精準、安全等顯著優勢。當前&#xff0c;巡檢機器人已從單純的數據采集階段邁向對采集數據進行深度分析的新階段。本文探討了巡檢機器人替代人工巡檢的現狀及優勢&#xff0c…

在 Flink + Kafka 實時數倉中,如何確保端到端的 Exactly-Once

在 Flink Kafka 構建實時數倉時&#xff0c;確保端到端的 Exactly-Once&#xff08;精確一次&#xff09; 需要從 數據消費&#xff08;Source&#xff09;、處理&#xff08;Processing&#xff09;、寫入&#xff08;Sink&#xff09; 三個階段協同設計&#xff0c;結合 Fli…

當可視化遇上 CesiumJS:突破傳統,打造前沿生產配套方案

CesiumJS 技術基礎介紹 CesiumJS 是一款基于 JavaScript 的開源庫&#xff0c;專門用于創建動態、交互式的地理空間可視化。它利用 WebGL 技術&#xff0c;能夠在網頁瀏覽器中流暢地渲染高分辨率的三維地球和地圖場景。CesiumJS 支持多種地理空間數據格式&#xff0c;包括但不…

RabbitMQ深入學習

繼續上一節的學習&#xff0c;上一節學習了RabbitMQ的基本內容&#xff0c;本節學習RabbitMQ的高級特性。 RocketMQ的高級特性學習見這篇博客 目錄 1.消息可靠性1.1生產者消息確認1.2消息持久化1.3消費者消息確認1.4消費失敗重試機制1.5消息可靠性保證總結 2.什么是死信交換機…

Linux系統:虛擬文件系統與文件緩沖區(語言級內核級)

本節重點 初步理解一切皆文件理解文件緩沖區的分類用戶級文件緩沖區與內核級文件緩沖區用戶級文件緩沖區的刷新機制兩級緩沖區的分層協作 一、虛擬文件系統 1.1 理解“一切皆文件” 我們都知道操作系統訪問不同的外部設備&#xff08;顯示器、磁盤、鍵盤、鼠標、網卡&#…

在c++中老是碰到string,這是什么意思?

定義一個string類型變量的引用&#xff0c;相當于給現有變量起個別名&#xff0c;與指針還是不一樣的。比如string a;string& ba;這兩句&#xff0c;b與a實際上是一回事&#xff0c;表示的是同一塊內存。 std是系統的一個命名空間(有關命名空間可以參閱namespace_百度百科)…

Day21 奇異值分解(SVD)全面解析

一、奇異值分解概述 奇異值分解是線性代數中一個重要的矩陣分解方法&#xff0c;對于任何矩陣&#xff0c;無論是結構化數據轉化成的“樣本 * 特征”矩陣&#xff0c;還是天然以矩陣形式存在的圖像數據&#xff0c;都能進行等價的奇異值分解&#xff08;SVD&#xff09;。 二…

akshare爬蟲限制,pywencai頻繁升級個人做量化,穩定數據源和券商的選擇

做量化&#xff0c;數據和交易接口是策略和自動化交易的基石&#xff0c;而穩定的數據和快人一步的交易接口是個人做量化的催化劑。 之前寫過一篇文章&#xff1a;個人做量化常用的數據&#xff0c;多以爬蟲為主&#xff0c;最近akshare爬蟲限制&#xff0c;pywencai頻繁升級。…

數字簽名與證書

1. 數字簽名與證書 摘要算法用來實現完整性&#xff0c;能夠為數據生成獨一無二的“指紋”&#xff0c;常用的算法是 SHA-2&#xff1b;數字簽名是私鑰對摘要的加密&#xff0c;可以由公鑰解密后驗證&#xff0c;實現身份認證和不可否認&#xff1b;公鑰的分發需要使用數字證書…

Ubuntu22.04安裝顯卡驅動/卸載顯卡驅動

報錯 今日輸入nvidia-smi報錯,在安裝了535和550,包括560都沒辦法解決,但是又怕亂搞導致環境損壞,打算把顯卡卸載然后重新安裝系統默認推薦版本的顯卡驅動 qinqin:~$ nvidia-smi Failed to initialize NVML: Driver/library version mismatch NVML library version: 560.35卸載…

Web 架構之負載均衡全解析

文章目錄 一、引言二、思維導圖三、負載均衡的定義與作用定義作用1. 提高可用性2. 增強性能3. 實現擴展性 四、負載均衡類型硬件負載均衡代表設備優缺點 軟件負載均衡應用層負載均衡代表軟件優缺點 網絡層負載均衡代表軟件優缺點 五、負載均衡算法輪詢算法&#xff08;Round Ro…

linux下的Redis的編譯安裝與配置

配合做開發經常會用到redis&#xff0c;整理下編譯安裝配置過程&#xff0c;僅供參考&#xff01; --------------------------------------Redis的安裝與配置-------------------------------------- 下載 wget https://download.redis.io/releases/redis-6.2.6.tar.gz tar…