線程間通信是併發編程核心難點。本文通過同一生產者-消費者場景,實戰對比 while 輪詢、wait/notify、CountDownLatch 三種模式的代碼實現、CPU 開銷、適用場景、避坑指南。
// 共享數據結構 Listlist = new ArrayList<>(); // 生產者:每 500ms 添加 1 個元素,共 10 個 // 消費者:監聽 size==5 時停止並拋異常模擬業務終止
// 消費者線程
while (true) {
if (list.size() == 5) {
System.out.println("收到通知,停止");
throw new RuntimeException();
}
// 缺失 sleep → 100% CPU 空轉
// 加 sleep → 延遲響應、難以精確控制
}
final Object lock = new Object();
// 生產者
synchronized (lock) {
for (int i=0; i<10; i++) {
list.add("item");
if (list.size() == 5) {
lock.notify(); // 喚醒一個等待線程,不釋放鎖
}
Thread.sleep(500);
}
}
// 消費者 (必須先執行獲取鎖)
synchronized (lock) {
if (list.size() != 5) {
lock.wait(); // 釋放鎖,進入 WAITING 狀態
}
System.out.println("收到通知,停止");
throw new RuntimeException();
}
IllegalMonitorStateExceptionwhile (condition) wait() 而非 iffinal CountDownLatch latch = new CountDownLatch(1); // 計數器初始 1
// 生產者
for (int i=0; i<10; i++) {
list.add("item");
if (list.size() == 5) {
latch.countDown(); // 計數器 -1,歸零釋放所有 await
}
Thread.sleep(500);
}
// 消費者
if (list.size() != 5) {
latch.await(); // 阻塞直到計數器為 0
}
System.out.println("收到通知,停止");
throw new RuntimeException();
await(5, TimeUnit.SECONDS) 防死等| 工具 | 核心能力 | 適用場景 |
|---|---|---|
| CyclicBarrier | 可重置的計數器,攔截點攔住所有線程 | 多線程分階段計算、並行測試 |
| Phaser | 動態註冊/取消註冊、分階段同步 | 動態參與者數量的多階段任務 |
| Semaphore | 許可證控制並發訪問數 | 資源池限流、連接池 |
| Exchanger | 雙線程數據交換 | 生產者-消費者雙緩衝交換 |
| CompletableFuture | 異步組合、回調地獄終結者 | 現代異步編程首選 |
| 場景 | 推薦方案 | 理由 |
|---|---|---|
| 單次事件通知 (啟動、關閉、就緒) | CountDownLatch | 語義清晰、無鎖煩惱、支持超時 |
| 循環/重複的生產者-消費者 | BlockingQueue / ArrayBlockingQueue | 內置阻塞、無需手動同步、JUC 標準 |
| 多線程分階段同步等待 | CyclicBarrier / Phaser | 支持重置/動態參與者 |
| 低級鎖優化、非阻塞算法 | wait/notify / LockSupport | 極致性能、框架開發 |
| 現代業務異步編排 | CompletableFuture / Reactor / RxJava | 聲明式、組合式、異常傳播完善 |
線程通信本質是內存可見性 + 阻塞喚醒。掌握 JUC 同步器工具箱,拋棄底層 wait/notify,是寫出正確、高效、可維護併發代碼的捷徑。