Condition 核心優勢
java.util.concurrent.locks.Condition 將 Object 監視器方法(wait/notify/notifyAll)分離為獨立物件,配合 Lock 實現:
- 單一鎖關聯多個 Condition(多等待隊列)
- 精準喚醒特定條件的執行緒
- 避免虛假喚醒導致的效能浪費
API 對照表
| Object 方法 | Condition 方法 | 說明 |
| wait() | await() | 釋放鎖並阻塞 |
| wait(timeout) | await(time, unit) | 超時阻塞 |
| notify() | signal() | 喚醒單一執行緒 |
| notifyAll() | signalAll() | 喚醒所有執行緒 |
基礎範例:生產者-消費者模式
class BoundedBuffer {
final Lock lock = new ReentrantLock();
final Condition notFull = lock.newCondition(); // 寫入等待條件
final Condition notEmpty = lock.newCondition(); // 讀取等待條件
final Object[] items = new Object[100];
int putPtr = 0, takePtr = 0, count = 0;
public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length) {
notFull.await(); // 隊列滿,阻塞寫入執行緒
}
items[putPtr] = x;
if (++putPtr == items.length) putPtr = 0;
++count;
notEmpty.signal(); // 喚醒一個讀取執行緒
} finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0) {
notEmpty.await(); // 隊列空,阻塞讀取執行緒
}
Object x = items[takePtr];
if (++takePtr == items.length) takePtr = 0;
--count;
notFull.signal(); // 喚醒一個寫入執行緒
return x;
} finally {
lock.unlock();
}
}
}
傳統 wait/notify 版本對比
class TraditionalBuffer {
final Object[] items = new Object[100];
int putPtr = 0, takePtr = 0, count = 0;
public synchronized void put(Object x) throws InterruptedException {
while (count == items.length) wait(); // 單一等待隊列
items[putPtr] = x;
if (++putPtr == items.length) putPtr = 0;
++count;
notifyAll(); // 唤醒所有,含不必要的執行緒
}
public synchronized Object take() throws InterruptedException {
while (count == 0) wait();
Object x = items[takePtr];
if (++takePtr == items.length) takePtr = 0;
--count;
notifyAll(); // 同樣喚醒所有
return x;
}
}
關鍵差異分析
| 場景 | Condition | wait/notify |
| 隊列滿時 | 僅阻塞寫入執行緒 (notFull) | 阻塞所有執行緒 |
| 隊列空時 | 僅阻塞讀取執行緒 (notEmpty) | 阻塞所有執行緒 |
| 喚醒策略 | 精準 signal 特定隊列 | notifyAll 驚群效應 |
| 鎖綁定 | 顯式 Lock 物件 | 隱式 this 監視器 |
| 多條件支援 | 原生支援多 Condition | 需額外物件協調 |
進階應用:多階段管線處理
class Pipeline {
final Lock lock = new ReentrantLock();
final Condition stage1Done = lock.newCondition();
final Condition stage2Done = lock.newCondition();
final Condition stage3Done = lock.newCondition();
enum Stage { STAGE1, STAGE2, STAGE3, DONE }
Stage currentStage = Stage.STAGE1;
public void executeStage1() throws InterruptedException {
lock.lock();
try {
while (currentStage != Stage.STAGE1) stage1Done.await();
// 執行階段 1 邏輯
currentStage = Stage.STAGE2;
stage2Done.signal();
} finally { lock.unlock(); }
}
public void executeStage2() throws InterruptedException {
lock.lock();
try {
while (currentStage != Stage.STAGE2) stage2Done.await();
// 執行階段 2 邏輯
currentStage = Stage.STAGE3;
stage3Done.signal();
} finally { lock.unlock(); }
}
public void executeStage3() throws InterruptedException {
lock.lock();
try {
while (currentStage != Stage.STAGE3) stage3Done.await();
// 執行階段 3 邏輯
currentStage = Stage.DONE;
} finally { lock.unlock(); }
}
}
最佳實踐原則
- 必須在 Lock 保護下呼叫:await/signal 前需持有鎖
- 使用 while 迴圈檢查條件:防範虛假喚醒(雖 Condition 較少見)
- 釋放鎖用 finally 區塊:確保異常時也能釋放
- 優先 signal 而非 signalAll:減少不必要的執行緒競爭
- 命名語意化:notEmpty/notFull 比 cond1/cond2 更易維護
常見錯誤避坑
| 錯誤 | 後果 | 修正 |
| 未持有鎖呼叫 await() | IllegalMonitorStateException | 確保 lock.lock() 後再呼叫 |
| 使用 if 而非 while 檢查條件 | 虛假喚醒導致邏輯錯誤 | 改用 while 迴圈 |
| signal 後未釋放鎖 | 被喚醒執行緒無法取得鎖 | 在 finally 中 unlock |
| Condition 物件跨鎖使用 | 行為未定義 | 每個 Condition 專屬單一 Lock |