線程間的交互不僅限於互斥(鎖),更多時候需要協作:生產者-消費者模型、任務編排、資源池管理等。Java 提供了內置條件隊列(Object.wait/notify)、顯式條件隊列(Lock + Condition)以及高層同步工具類(BlockingQueue、CountDownLatch、Semaphore、CyclicBarrier)來優雅地解決協作問題。
Object.wait/notify/notifyAll每個 Java 對象天然擁有一把內置鎖(Monitor)和一個內置條件隊列。
wait():釋放當前鎖,線程進入 WAITING 狀態,直到被 notify/notifyAll 喚醒或中斷,喚醒後重新競爭鎖notify():喚醒條件隊列中任意一個等待線程notifyAll():喚醒條件隊列中所有等待線程,只有一個能獲取鎖繼續執行IllegalMonitorStateExceptionwhile (條件不滿足) wait(); 防止虛假喚醒與通知遺漏若通知線程在等待線程調用 wait() 之前執行 notify(),等待線程將永久阻塞。解決方案:引入狀態變量配合 while 循環:
// 等待線程
synchronized (lock) {
while (!okToProceed) {
lock.wait();
}
// 執行任務...
}
// 通知線程
synchronized (lock) {
okToProceed = true; // 先更新狀態
lock.notifyAll(); // 再通知
}
即使通知發生在等待之前,狀態變量已為 true,等待線程檢查條件後直接繼續執行,避免阻塞。
Lock + ConditionReentrantLock 的 newCondition() 可創建多個獨立的 Condition 對象,每個對應一種等待條件(如「非滿」、「非空」),功能更強大:
await() 拋出 InterruptedException)、超時等待(awaitNanos)、公平/非公平隊列await() / signal() / signalAll()public class ConditionBoundBuffer{ private final Lock lock = new ReentrantLock(); private final Condition notFull = lock.newCondition(); // 緩衝區非滿 private final Condition notEmpty = lock.newCondition(); // 緩衝區非空 private final T[] items = (T[]) new Object[100]; private int tail, head, count; public void put(T x) throws InterruptedException { lock.lock(); try { while (count == items.length) notFull.await(); // 等待非滿 items[tail] = x; if (++tail == items.length) tail = 0; ++count; notEmpty.signal(); // 喚醒消費者 } finally { lock.unlock(); } } public T take() throws InterruptedException { lock.lock(); try { while (count == 0) notEmpty.await(); // 等待非空 T x = items[head]; items[head] = null; if (++head == items.length) head = 0; --count; notFull.signal(); // 喚醒生產者 return x; } finally { lock.unlock(); } } }
高層工具類封裝了底層同步細節,開箱即用,降低併發編程難度。
線程安全、內部用 ReentrantLock + Condition 實現阻塞/喚醒,支援可中斷操作。
| 實現類 | 特點 | 適用場景 |
|---|---|---|
ArrayBlockingQueue | 數組、有界、FIFO | 固定大小緩衝區,如線程池任務隊列 |
LinkedBlockingQueue | 鏈表、可選有界、FIFO | 高吞吐生產消費,預設無界(Integer.MAX_VALUE) |
PriorityBlockingQueue | 優先級排序、無界 | 任務優先級調度 |
SynchronousQueue | 零容量、直接移交 | 線程間直接數據傳遞,CachedThreadPool 內部使用 |
核心方法:put(e)(滿則阻塞)、take()(空則阻塞)、offer(e)/poll()(非阻塞)、poll(timeout)(超時)。
「一扇門,需按 N 次才能開」。主線程 await() 等待,N 個工作線程完成後各自 countDown(),計數歸零門開啟,所有等待線程釋放。
CountDownLatch latch = new CountDownLatch(N); // 工作線程 latch.countDown(); // 主線程 latch.await(); // 阻塞直到計數為 0
典型場景:應用啟動等待多個初始化任務、並行計算聚合結果、壓測並發起點同步。
「資源池許可證」。初始許可數 = 可用資源數。acquire() 獲取許可(無則阻塞),release() 歸還許可。
Semaphore sem = new Semaphore(10); // 10 個資源
sem.acquire(); // 佔用一個
try { /* 使用資源 */ } finally { sem.release(); } // 歸還
應用:數據庫連接池、限流器、控制併發任務數。0-1 信號量可作互斥鎖。
「所有人到齊再出發」。N 個線程各自執行至 barrier.await() 阻塞,最後一個到達觸發可選的 Runnable 任務,然後所有線程同時繼續。柵欄可重複使用(reset())。
CyclicBarrier barrier = new CyclicBarrier(N, () -> System.out.println("全員到齊,開始比賽"));
// 每個線程
barrier.await(); // 等待其他線程
區別:CountDownLatch 等待事件(計數歸零),CyclicBarrier 等待線程(全員到達),且可循環。
掌握條件隊列與同步工具類,是編寫高性能、可維護併發程序的關鍵。優先使用高層工具類(BlockingQueue 等),它們經過專家優化、測試充分;僅在工具類無法滿足需求時,才降級使用 Lock/Condition 或 wait/notify。