Java 監視器支援兩種線程互動:互斥(前文介紹的對象鎖和重入鎖)和協作。本文探討線程協作機制。
3 顧客(消費者)、3 廚師(生產者)、1 服務員(協調者)。容器裝 10 個漢堡,先進先出。容器滿→廚師停工;容器空→顧客按鬧鈴→服務員安撫等待;漢堡做好→服務員通知顧客取餐。容器是決定廚師行為的監視器,服務員監視顧客行為。JVM 中此監視器稱為「等待並喚醒監視器」。
wait() 暫停執行、釋放監視器、進入等待區notify() 喚醒等待區線程wait/notify/notifyAll 必須先取得該對象控制權IllegalMonitorStateException取得對象控制權三種方式:同步實例方法、同步靜態方法、對該對象加同步鎖的同步塊。
// 錯誤
flag = "false";
flag.notify();
// 正確
synchronized (flag) {
flag = "false";
flag.notify();
}
synchronized (flag) { flag = "false"; } 導致同步塊鎖定的物件改變。解決:用陣列或 JavaBean 封裝,修改內部屬性而非引用。
private String[] flag = {"true"};
synchronized (flag) {
flag[0] = "false";
flag.notify();
}
notify() 隨機喚醒一線程;notifyAll() 喚醒所有阻塞線程。範例中需用 notifyAll() 確保所有顧客被喚醒。
程式包含:Waiter(服務員)、Hamberg(漢堡)、HambergFifo(容器)、Cooker(廚師)、Customer(顧客)、HambergShop(主類)。
關鍵協作邏輯:
waiter.notifyAll() 通知顧客;容器滿→pool.wait() 等待pool.pop();容器空→拋異常→pool.notifyAll() 通知廚師→waiter.wait() 等待服務員通知若用 ReentrantLock.lock()/unlock() 替代 synchronized,仍會拋 IllegalMonitorStateException,甚至造成死鎖。原因:調用第三方對象 wait/notify 時未進入該對象監視器。正確做法:只用 ReentrantLock 且不使用 wait/notify,因 ReentrantLock 已概括互斥與協作。請單獨採用重入鎖或 synchronized,勿混用。