首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 多線程同步問題探究(四):線程協作與 wait/notify

     編輯:Java 相關知識

Java 監視器:互斥與協作

Java 監視器支援兩種線程互動:互斥(前文介紹的對象鎖和重入鎖)和協作。本文探討線程協作機制。

經典生產者-消費者範例:漢堡店大賽

3 顧客(消費者)、3 廚師(生產者)、1 服務員(協調者)。容器裝 10 個漢堡,先進先出。容器滿→廚師停工;容器空→顧客按鬧鈴→服務員安撫等待;漢堡做好→服務員通知顧客取餐。容器是決定廚師行為的監視器,服務員監視顧客行為。JVM 中此監視器稱為「等待並喚醒監視器」。

wait/notify 機制原理

  • 持有監視器的線程調用 wait() 暫停執行、釋放監視器、進入等待區
  • 其他線程調用 notify() 喚醒等待區線程
  • 喚醒線程持有監視器直到主動釋放
  • 等待線程甦醒,其中一個重新獲得監視器,判斷條件決定是否繼續等待、執行監視區域或退出

關鍵規則

  1. 任何時刻對象控制權只能被一線程擁有
  2. 執行 wait/notify/notifyAll 必須先取得該對象控制權
  3. 無控制權執行上述方法會拋 IllegalMonitorStateException
  4. JVM 預設不保證線程時序性

取得對象控制權三種方式:同步實例方法、同步靜態方法、對該對象加同步鎖的同步塊。

常見錯誤修正

錯誤 1:未在同步塊中調用 wait/notify

// 錯誤
flag = "false";
flag.notify();
// 正確
synchronized (flag) {
  flag = "false";
  flag.notify();
}

錯誤 2:同步物件被重新賦值

synchronized (flag) { flag = "false"; } 導致同步塊鎖定的物件改變。解決:用陣列或 JavaBean 封裝,修改內部屬性而非引用。

private String[] flag = {"true"};
synchronized (flag) {
  flag[0] = "false";
  flag.notify();
}

notify() vs notifyAll()

notify() 隨機喚醒一線程;notifyAll() 喚醒所有阻塞線程。範例中需用 notifyAll() 確保所有顧客被喚醒。

完整漢堡店範例

程式包含:Waiter(服務員)、Hamberg(漢堡)、HambergFifo(容器)、Cooker(廚師)、Customer(顧客)、HambergShop(主類)。

關鍵協作邏輯:

  • 廚師:容器未滿→製作漢堡→waiter.notifyAll() 通知顧客;容器滿→pool.wait() 等待
  • 顧客:取漢堡→pool.pop();容器空→拋異常→pool.notifyAll() 通知廚師→waiter.wait() 等待服務員通知

ReentrantLock 與 wait/notify 不能混用

若用 ReentrantLock.lock()/unlock() 替代 synchronized,仍會拋 IllegalMonitorStateException,甚至造成死鎖。原因:調用第三方對象 wait/notify 時未進入該對象監視器。正確做法:只用 ReentrantLock 且不使用 wait/notify,因 ReentrantLock 已概括互斥與協作。請單獨採用重入鎖或 synchronized,勿混用。

核心結論

  1. 線程等待/喚醒是調用共享對象的 wait/notify,非自身
  2. 調用對象 wait/notify 前必須先取得該對象監視器
  3. 線程協作以線程互斥為前提,是互斥下的協作
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved