在 Java 中,java.util.concurrent.locks.Condition
?接口提供了類似監視器的方法(await()
,?signal()
,?signalAll()
)來實現線程間的協調。正確使用?Condition
?對象需要遵循特定模式以避免常見問題。
常見問題及修復方案
1. 虛假喚醒問題
問題:線程可能在未收到信號的情況下從?await()
?返回(虛假喚醒),如果使用?if
?檢查條件,可能導致條件不滿足時繼續執行。
java
lock.lock(); try {if (!condition) { // ? 錯誤:使用if檢查條件condition.await();}// 執行操作 } finally {lock.unlock(); }
修復:始終使用?while
?循環檢查條件:
java
lock.lock(); try {while (!condition) { // ? 正確:循環檢查條件condition.await();}// 執行操作 } finally {lock.unlock(); }
2. 未持有鎖時調用方法
問題:在未持有鎖的情況下調用?await()
,?signal()
?或?signalAll()
?會拋出?IllegalMonitorStateException
。
java
Condition condition = lock.newCondition(); condition.await(); // ? 錯誤:未持有鎖
修復:確保在持有鎖時調用這些方法:
java
lock.lock(); try {condition.await(); // ? 正確:持有鎖 } finally {lock.unlock(); }
3. 信號丟失問題
問題:在調用?await()
?之前調用?signal()
?可能導致信號丟失。
java
// 線程A lock.lock(); try {condition.signal(); // ? 可能丟失信號 } finally {lock.unlock(); }// 線程B稍后調用await()
修復:確保在調用?await()
?之前已經檢查條件:
java
lock.lock(); try {while (!condition) {condition.await(); // ? 安全等待} } finally {lock.unlock(); }
4. 忘記喚醒等待線程
問題:修改條件后忘記調用?signal()
?或?signalAll()
,導致等待線程永遠阻塞。
java
lock.lock(); try {condition = true; // 修改條件// 忘記調用 signal() ? } finally {lock.unlock(); }
修復:在修改條件后立即發出信號:
java
lock.lock(); try {condition = true;condition.signalAll(); // ? 喚醒所有等待線程 } finally {lock.unlock(); }
5. 未處理中斷
問題:await()
?可能被中斷,如果未處理中斷,可能導致意外行為。
java
try {condition.await(); // ? 未處理中斷異常 } catch (InterruptedException e) {// 未處理中斷 }
修復:正確處理中斷:
java
try {condition.await(); } catch (InterruptedException e) {Thread.currentThread().interrupt(); // ? 重新設置中斷狀態// 處理中斷 }
6. 未使用超時導致永久阻塞
問題:如果條件永遠不滿足,線程可能永久阻塞。
java
condition.await(); // ? 可能永久阻塞
修復:使用帶超時的?await
?方法:
java
if (condition.await(5, TimeUnit.SECONDS)) {// 在超時前收到信號 } else {// 處理超時情況 ? }
7. 錯誤選擇 signal() 和 signalAll()
問題:在不合適的場景使用?signal()
?而不是?signalAll()
?可能遺漏喚醒。
java
condition.signal(); // ? 可能只喚醒一個線程,但多個線程在等待
修復:根據需求選擇合適的喚醒方法:
java
// 當只需要喚醒一個線程時 condition.signal(); // ? 喚醒單個線程// 當需要喚醒所有等待線程時 condition.signalAll(); // ? 喚醒所有線程
完整正確示例
java
import java.util.concurrent.locks.*;public class ConditionExample {private final Lock lock = new ReentrantLock();private final Condition condition = lock.newCondition();private boolean resourceReady = false;public void consumer() {lock.lock();try {// 循環檢查條件,防止虛假喚醒while (!resourceReady) {try {// 等待并處理中斷condition.await();} catch (InterruptedException e) {Thread.currentThread().interrupt();// 處理中斷邏輯return;}}// 資源就緒,執行操作System.out.println("Resource consumed");} finally {lock.unlock();}}public void producer() {lock.lock();try {// 準備資源resourceReady = true;// 喚醒所有等待線程condition.signalAll();System.out.println("Resource produced");} finally {lock.unlock();}}public static void main(String[] args) {ConditionExample example = new ConditionExample();// 創建消費者線程Thread consumerThread = new Thread(example::consumer);consumerThread.start();// 稍等片刻try {Thread.sleep(1000);} catch (InterruptedException e) {Thread.currentThread().interrupt();}// 創建生產者線程new Thread(example::producer).start();} }
最佳實踐總結
始終使用 while 循環檢查條件:防止虛假喚醒
確保持有鎖:在調用?
await()
,?signal()
?或?signalAll()
?前必須持有鎖在 finally 塊中釋放鎖:防止死鎖
正確處理中斷:捕獲?
InterruptedException
?并重新設置中斷狀態使用超時機制:避免永久阻塞
明確信號選擇:
signal()
:當只需喚醒一個線程時使用signalAll()
:當需要喚醒所有等待線程時使用
條件變量與謂詞關聯:每個條件變量應該關聯一個明確的條件謂詞
遵循這些模式可以避免使用?Condition
?時的常見陷阱,確保線程安全協調。