Java并发利器:CountDownLatch深度解析与实战应用
生活随笔
收集整理的這篇文章主要介紹了
Java并发利器:CountDownLatch深度解析与实战应用
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
Java并發利器:CountDownLatch深度解析與實戰應用
多線程編程中,讓主線程等待所有子任務完成是個常見需求。CountDownLatch就像一個倒計時器,當所有任務完成后,主線程才繼續執行。本文將通過簡單易懂的方式,帶你掌握這個強大的并發工具。
一、CountDownLatch是什么?
1. 基本概念
CountDownLatch就是一個"倒計數門閂":
- 倒計數:從指定數字開始遞減到0
- 門閂:當計數為0時,門閂打開,等待的線程繼續執行
- 一次性:用完即棄,不能重置
graph TD
A[創建CountDownLatch 3] --> B[啟動3個任務]
B --> C[任務1完成 countDown]
B --> D[任務2完成 countDown]
B --> E[任務3完成 countDown]
C --> F{計數器=0?}
D --> F
E --> F
F -->|是| G[主線程繼續執行]
F -->|否| H[繼續等待]
A[創建CountDownLatch 3] --> B[啟動3個任務]
B --> C[任務1完成 countDown]
B --> D[任務2完成 countDown]
B --> E[任務3完成 countDown]
C --> F{計數器=0?}
D --> F
E --> F
F -->|是| G[主線程繼續執行]
F -->|否| H[繼續等待]
2. 基本用法
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
// 創建計數器,初始值為3
CountDownLatch latch = new CountDownLatch(3);
// 啟動3個任務
for (int i = 0; i < 3; i++) {
final int taskId = i;
new Thread(() -> {
System.out.println("任務" + taskId + "開始執行");
try {
Thread.sleep(2000); // 模擬任務執行
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("任務" + taskId + "執行完成");
latch.countDown(); // 計數器減1
}).start();
}
System.out.println("主線程等待所有任務完成...");
latch.await(); // 等待計數器變為0
System.out.println("所有任務完成,主線程繼續執行");
}
}
運行結果:
主線程等待所有任務完成...
任務0開始執行
任務1開始執行
任務2開始執行
任務0執行完成
任務1執行完成
任務2執行完成
所有任務完成,主線程繼續執行
二、核心API介紹
CountDownLatch只有4個關鍵方法:
public class CountDownLatchAPI {
public void demonstrateAPI() throws InterruptedException {
CountDownLatch latch = new CountDownLatch(3);
// 1. countDown() - 計數器減1
latch.countDown();
// 2. await() - 等待計數器變為0
latch.await();
// 3. await(時間, 單位) - 超時等待
boolean finished = latch.await(5, TimeUnit.SECONDS);
// 4. getCount() - 獲取當前計數值
long count = latch.getCount();
System.out.println("剩余計數: " + count);
}
}
三、經典應用場景
場景1:等待多個任務完成
最常用的場景,主線程等待所有子任務完成:
public class WaitMultipleTasksDemo {
// 模擬訂單處理:需要等待庫存檢查、用戶驗證、支付驗證都完成
public void processOrder(String orderId) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(3);
// 庫存檢查
new Thread(() -> {
try {
System.out.println("開始庫存檢查...");
Thread.sleep(1000);
System.out.println("庫存檢查完成");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
latch.countDown();
}
}).start();
// 用戶驗證
new Thread(() -> {
try {
System.out.println("開始用戶驗證...");
Thread.sleep(1500);
System.out.println("用戶驗證完成");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
latch.countDown();
}
}).start();
// 支付驗證
new Thread(() -> {
try {
System.out.println("開始支付驗證...");
Thread.sleep(800);
System.out.println("支付驗證完成");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
latch.countDown();
}
}).start();
System.out.println("等待所有驗證完成...");
latch.await();
System.out.println("訂單處理完成: " + orderId);
}
}
場景2:控制并發啟動
讓多個線程同時開始執行:
public class ConcurrentStartDemo {
// 模擬賽跑:所有選手同時起跑
public void startRace() throws InterruptedException {
int runnerCount = 5;
CountDownLatch startGun = new CountDownLatch(1); // 發令槍
CountDownLatch finish = new CountDownLatch(runnerCount); // 終點線
// 創建選手
for (int i = 0; i < runnerCount; i++) {
final int runnerId = i;
new Thread(() -> {
try {
System.out.println("選手" + runnerId + "準備就緒");
startGun.await(); // 等待發令槍
// 開始跑步
System.out.println("選手" + runnerId + "開始跑步");
Thread.sleep(new Random().nextInt(3000)); // 模擬跑步時間
System.out.println("選手" + runnerId + "到達終點");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
finish.countDown();
}
}).start();
}
Thread.sleep(2000); // 等待選手準備
System.out.println("預備...開始!");
startGun.countDown(); // 發令
finish.await(); // 等待所有選手完成
System.out.println("比賽結束!");
}
}
場景3:分段計算
將大任務拆分成小任務并行計算:
public class ParallelCalculationDemo {
// 并行計算數組的和
public long calculateSum(int[] array) throws InterruptedException {
int threadCount = 4;
CountDownLatch latch = new CountDownLatch(threadCount);
AtomicLong totalSum = new AtomicLong(0);
int chunkSize = array.length / threadCount;
for (int i = 0; i < threadCount; i++) {
final int start = i * chunkSize;
final int end = (i == threadCount - 1) ? array.length : (i + 1) * chunkSize;
new Thread(() -> {
long partialSum = 0;
for (int j = start; j < end; j++) {
partialSum += array[j];
}
totalSum.addAndGet(partialSum);
System.out.println("線程計算范圍[" + start + "," + end + "),結果:" + partialSum);
latch.countDown();
}).start();
}
latch.await();
return totalSum.get();
}
public static void main(String[] args) throws InterruptedException {
int[] array = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
ParallelCalculationDemo demo = new ParallelCalculationDemo();
long result = demo.calculateSum(array);
System.out.println("總和:" + result);
}
}
四、使用注意事項
1. 異常處理要點
核心原則:無論是否異常,都要調用countDown()
// 正確寫法
new Thread(() -> {
try {
// 業務邏輯
doSomething();
} catch (Exception e) {
System.err.println("任務異常:" + e.getMessage());
} finally {
latch.countDown(); // 確保在finally中調用
}
}).start();
// 錯誤寫法
new Thread(() -> {
try {
doSomething();
latch.countDown(); // 異常時不會執行,導致死鎖
} catch (Exception e) {
System.err.println("任務異常:" + e.getMessage());
// 忘記調用countDown()
}
}).start();
2. 避免無限等待
// 設置超時時間,避免無限等待
boolean finished = latch.await(10, TimeUnit.SECONDS);
if (finished) {
System.out.println("所有任務完成");
} else {
System.out.println("等待超時,可能有任務失敗");
}
3. 合理使用線程池
public void useWithThreadPool() throws InterruptedException {
CountDownLatch latch = new CountDownLatch(5);
ExecutorService executor = Executors.newFixedThreadPool(3);
for (int i = 0; i < 5; i++) {
final int taskId = i;
executor.submit(() -> {
try {
System.out.println("執行任務" + taskId);
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
latch.countDown();
}
});
}
latch.await();
executor.shutdown(); // 關閉線程池
System.out.println("所有任務完成");
}
五、實際項目案例
案例:系統啟動初始化
public class SystemInitializer {
public boolean initializeSystem() {
System.out.println("開始系統初始化...");
CountDownLatch latch = new CountDownLatch(4);
AtomicBoolean success = new AtomicBoolean(true);
// 數據庫初始化
new Thread(() -> {
try {
System.out.println("初始化數據庫連接...");
Thread.sleep(2000);
System.out.println("數據庫初始化完成");
} catch (InterruptedException e) {
success.set(false);
} finally {
latch.countDown();
}
}).start();
// Redis初始化
new Thread(() -> {
try {
System.out.println("初始化Redis連接...");
Thread.sleep(1000);
System.out.println("Redis初始化完成");
} catch (InterruptedException e) {
success.set(false);
} finally {
latch.countDown();
}
}).start();
// 配置加載
new Thread(() -> {
try {
System.out.println("加載系統配置...");
Thread.sleep(800);
System.out.println("配置加載完成");
} catch (InterruptedException e) {
success.set(false);
} finally {
latch.countDown();
}
}).start();
// 服務注冊
new Thread(() -> {
try {
System.out.println("注冊服務...");
Thread.sleep(1500);
System.out.println("服務注冊完成");
} catch (InterruptedException e) {
success.set(false);
} finally {
latch.countDown();
}
}).start();
try {
boolean finished = latch.await(10, TimeUnit.SECONDS);
if (finished && success.get()) {
System.out.println("系統初始化成功!");
return true;
} else {
System.out.println("系統初始化失敗!");
return false;
}
} catch (InterruptedException e) {
System.out.println("初始化被中斷");
return false;
}
}
public static void main(String[] args) {
SystemInitializer initializer = new SystemInitializer();
initializer.initializeSystem();
}
}
六、總結
CountDownLatch是Java并發編程中的實用工具,它的核心價值在于:
核心特點
- 簡單易用:API簡潔,概念清晰
- 線程安全:內部實現保證多線程安全
- 靈活應用:適合多種并發協作場景
使用要點
- 異常安全:在finally中調用countDown()
- 超時控制:使用帶超時的await()方法
- 一次性使用:CountDownLatch不能重置
- 合理設計:根據實際任務數量設置計數器
適用場景
- 主線程等待多個子任務完成
- 控制多個線程同時開始執行
- 分段并行計算后匯總結果
- 系統啟動時的組件初始化
掌握CountDownLatch,讓你的多線程程序更加優雅和高效!
覺得文章有用?歡迎關注我的微信公眾號【一只劃水的程序猿】,持續分享Java并發編程、性能優化等技術干貨,一起在技術路上精進成長!
總結
以上是生活随笔為你收集整理的Java并发利器:CountDownLatch深度解析与实战应用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 钢铁雄心4瑞士军事准备度
- 下一篇: RabbitMq安装、配置