多線程協作中,如何控制執行順序?本文對比四種調度方案:**Thread.join()、Object.wait/notify、CountDownLatch、CyclicBarrier**,分析適用場景、優缺點與代碼範例,並給出生產級選型建議。
Object lock = new Object();
boolean ready = false;
// 生產者
synchronized (lock) { prepare(); ready = true; lock.notify(); }
// 消費者
synchronized (lock) { while (!ready) lock.wait(); consume(); }
class DecryptService {
private final Map holders = new ConcurrentHashMap<>();
public String decryptAsync(Long fid, File encFile) {
CountDownLatch latch = new CountDownLatch(1);
holders.put(fid, new DecryptHolder(latch));
executor.submit(() -> {
try { String path = decrypt(encFile); holder.setPath(path); }
finally { latch.countDown(); }
});
return latch; // 返回 latch 供調用者 await
}
public String getDecryptedPath(Long fid) throws InterruptedException {
DecryptHolder h = holders.get(fid);
h.latch.await(); // 等待解密完成
return h.path;
}
static class DecryptHolder { CountDownLatch latch; String path; ... }
}
{ prepare(); barrier.await(); race(); }).start();
}
reset() 重置計數 (會觸發 BrokenBarrierException)。await() 拋異常/中斷,其他線程收到 BrokenBarrierException。| 特性 | join() | wait/notify | CountDownLatch | CyclicBarrier |
|---|---|---|---|---|
| 等待對象 | Thread 終止 | 任意條件 (狀態) | 計數器歸零 | 所有參與者到達 |
| 可重用 | 否 (線程不可重啟) | 是 | 否 | 是 (reset()) |
| 同步模式 | 一對一 | 靈活 | 多對一 (聚合) | 多對多 (齊聚) |
| 攔截動作 | 無 | 手工實現 | 無 | 支援 (Runnable) |
| 異常傳播 | InterruptedException | InterruptedException | InterruptedException | InterruptedException + BrokenBarrierException |
| 典型場景 | 主線程等初始化 | 生產消費、狀態機 | 並行任務聚合、啟動等待 | 分階段並行計算、賽馬場景 |
register(), arriveAndDeregister())。getPhase()),可跨代換任務。join() (或 Future.get() 配合線程池)。CountDownLatch (或 CompletableFuture.allOf())。CyclicBarrier / Phaser。BlockingQueue / wait/notify (底層) / Lock + Condition。CompletableFuture, parallelStream, ExecutorService.invokeAll。線程調度工具從低層 join/wait/notify 進化到高層 CountDownLatch/CyclicBarrier/Phaser/CompletableFuture。**核心原則**:用最高層級能滿足需求的 API,避免手工管理鎖與狀態。理解各工具的語義邊界 (一次性 vs 可重用、聚合 vs 齊聚) 是正確選型的關鍵。