Java并發(fā)利器CountDownLatch深度解析與實戰(zhàn)應用小結
Java并發(fā)利器:CountDownLatch深度解析與實戰(zhàn)應用
多線程編程中,讓主線程等待所有子任務完成是個常見需求。CountDownLatch就像一個倒計時器,當所有任務完成后,主線程才繼續(xù)執(zhí)行。本文將通過簡單易懂的方式,帶你掌握這個強大的并發(fā)工具。
一、CountDownLatch是什么?
1. 基本概念
CountDownLatch就是一個"倒計數門閂":
- 倒計數:從指定數字開始遞減到0
- 門閂:當計數為0時,門閂打開,等待的線程繼續(xù)執(zhí)行
- 一次性:用完即棄,不能重置
2. 基本用法
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
// 創(chuàng)建計數器,初始值為3
CountDownLatch latch = new CountDownLatch(3);
// 啟動3個任務
for (int i = 0; i < 3; i++) {
final int taskId = i;
new Thread(() -> {
System.out.println("任務" + taskId + "開始執(zhí)行");
try {
Thread.sleep(2000); // 模擬任務執(zhí)行
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("任務" + taskId + "執(zhí)行完成");
latch.countDown(); // 計數器減1
}).start();
}
System.out.println("主線程等待所有任務完成...");
latch.await(); // 等待計數器變?yōu)?
System.out.println("所有任務完成,主線程繼續(xù)執(zhí)行");
}
}運行結果:
主線程等待所有任務完成...
任務0開始執(zhí)行
任務1開始執(zhí)行
任務2開始執(zhí)行
任務0執(zhí)行完成
任務1執(zhí)行完成
任務2執(zhí)行完成
所有任務完成,主線程繼續(xù)執(zhí)行
二、核心API介紹
CountDownLatch只有4個關鍵方法:
public class CountDownLatchAPI {
public void demonstrateAPI() throws InterruptedException {
CountDownLatch latch = new CountDownLatch(3);
// 1. countDown() - 計數器減1
latch.countDown();
// 2. await() - 等待計數器變?yōu)?
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:控制并發(fā)啟動
讓多個線程同時開始執(zhí)行:
public class ConcurrentStartDemo {
// 模擬賽跑:所有選手同時起跑
public void startRace() throws InterruptedException {
int runnerCount = 5;
CountDownLatch startGun = new CountDownLatch(1); // 發(fā)令槍
CountDownLatch finish = new CountDownLatch(runnerCount); // 終點線
// 創(chuàng)建選手
for (int i = 0; i < runnerCount; i++) {
final int runnerId = i;
new Thread(() -> {
try {
System.out.println("選手" + runnerId + "準備就緒");
startGun.await(); // 等待發(fā)令槍
// 開始跑步
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(); // 發(fā)令
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 {
// 業(yè)務邏輯
doSomething();
} catch (Exception e) {
System.err.println("任務異常:" + e.getMessage());
} finally {
latch.countDown(); // 確保在finally中調用
}
}).start();
// ? 錯誤寫法
new Thread(() -> {
try {
doSomething();
latch.countDown(); // 異常時不會執(zhí)行,導致死鎖
} 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("執(zhí)行任務" + taskId);
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
latch.countDown();
}
});
}
latch.await();
executor.shutdown(); // 關閉線程池
System.out.println("所有任務完成");
}五、實際項目案例
案例:系統(tǒng)啟動初始化
public class SystemInitializer {
public boolean initializeSystem() {
System.out.println("開始系統(tǒng)初始化...");
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("加載系統(tǒng)配置...");
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("系統(tǒng)初始化成功!");
return true;
} else {
System.out.println("系統(tǒng)初始化失??!");
return false;
}
} catch (InterruptedException e) {
System.out.println("初始化被中斷");
return false;
}
}
public static void main(String[] args) {
SystemInitializer initializer = new SystemInitializer();
initializer.initializeSystem();
}
}六、總結
CountDownLatch是Java并發(fā)編程中的實用工具,它的核心價值在于:
?? 核心特點
- 簡單易用:API簡潔,概念清晰
- 線程安全:內部實現(xiàn)保證多線程安全
- 靈活應用:適合多種并發(fā)協(xié)作場景
?? 使用要點
- 異常安全:在finally中調用countDown()
- 超時控制:使用帶超時的await()方法
- 一次性使用:CountDownLatch不能重置
- 合理設計:根據實際任務數量設置計數器
?? 適用場景
- 主線程等待多個子任務完成
- 控制多個線程同時開始執(zhí)行
- 分段并行計算后匯總結果
- 系統(tǒng)啟動時的組件初始化
掌握CountDownLatch,讓你的多線程程序更加優(yōu)雅和高效!
到此這篇關于Java并發(fā)利器:CountDownLatch深度解析與實戰(zhàn)應用的文章就介紹到這了,更多相關Java并發(fā)利器:CountDownLatch深度解析與實戰(zhàn)應用內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Springboot整合Swagger2后訪問swagger-ui.html 404報錯問題解決方案
這篇文章主要介紹了Springboot整合Swagger2后訪問swagger-ui.html 404報錯,本文給大家分享兩種解決方案,結合實例代碼給大家介紹的非常詳細,需要的朋友可以參考下2023-06-06
springboot將mybatis升級為mybatis-plus的實現(xiàn)
之前項目工程用的是mybatis,現(xiàn)在需要將其替換為mybatis-plus,本文主要介紹了springboot將mybatis升級為mybatis-plus的實現(xiàn),具有一定的參考價值,感興趣的可以了解一下2023-09-09
Java中List<T> Map與Map List<T>的區(qū)別小結
本文主要介紹了Java中List<T> Map與Map List<T>的區(qū)別小結,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2023-08-08
Springcould多模塊搭建Eureka服務器端口過程詳解
這篇文章主要介紹了Springcould多模塊搭建Eureka服務器端口過程詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2019-11-11

