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

wait()/notify() 經典生產者-消費者模式實作解析

     編輯:Java 相關知識

Java 低層級執行緒協作:wait/notify 核心機制與範例

Object.wait()、notify()、notifyAll() 是 Java 物件監視器提供的基礎執行緒通訊原語。本文透過一個「司機-乘客-汽車」模擬範例,深入解析其正確使用模式、常見錯誤與現代替代方案。

範例程式碼解析

// 共享資源:汽車
class Car {
    synchronized public void sleep() {  // 乘客上車 → 司機等待
        try { wait(); } catch (Exception e) {}
    }
    synchronized public void wake() {   // 乘客下車 → 通知司機
        notify();
    }
}

// 乘客執行緒:週期性上車、下車
class Passenger implements Runnable {
    private Car car;
    public Passenger(Car c) { this.car = c; }
    public void run() {
        while (true) {
            Thread.yield();
            try { Thread.sleep(3000); } catch (InterruptedException ie) {}
            System.out.println("a passenger go on");
            car.wake();      // 通知司機有人上車
            System.out.println("waiting stop and sleep...");
            car.sleep();     // 乘客等待下車
        }
    }
}

// 司機執行緒:等待乘客、開車
class Driver implements Runnable {
    private Car car;
    public Driver(Car c) { this.car = c; }
    public void run() {
        while (true) {
            System.out.println("wait a passenger.......");
            car.sleep();     // 司機等待乘客
            try { Thread.sleep(3000); } catch (InterruptedException ie) {}
            Thread.yield();
            try { Thread.sleep(100); } catch (InterruptedException ie) {}
            car.wake();      // 通知乘客到站
            System.out.println(" passenger wake and go ");
        }
    }
}

// 主程式啟動
public class Road {
    public static void main(String[] args) {
        Car c = new Car();
        new Thread(new Driver(c)).start();
        new Thread(new Passenger(c)).start();
    }
}

關鍵機制深度解析

1. 同步前提

  • wait()/notify() 必須 在同步方法或同步區塊(synchronized(this))中呼叫
  • 否則拋出 IllegalMonitorStateException
  • 範例中兩方法均宣告 synchronized,滿足條件

2. wait() 語意

  1. 釋放持有的物件鎖(monitor)
  2. 當前執行緒進入該物件的「等待集合」,狀態變為 WAITING
  3. 直到:notify()/notifyAll()、interrupt()、或超時(wait(timeout))
  4. 被喚醒後,需重新競爭鎖,獲得鎖後從 wait() 返回繼續執行

3. notify() vs notifyAll()

  • notify():隨機喚醒一個等待執行緒
  • notifyAll():喚醒所有等待執行緒
  • 建議使用 notifyAll():避免「信號遺失」導致部分鎖定條件的執行緒永不被喚醒

4. 範例邏輯流向

  1. 司機先跑 → car.sleep() → wait() 釋放鎖、等待
  2. 乘客睡 3 秒 → car.wake() → notify() 喚醒司機
  3. 司機獲鎖繼續 → 睡 3 秒模擬開車 → car.wake() 喚醒乘客
  4. 乘客獲鎖繼續 → car.sleep() 等待下一趟

常見錯誤與最佳實務

錯誤/風險說明修正
未在同步區塊呼叫 wait/notify拋出 IllegalMonitorStateException確保在 synchronized 方法/區塊中
使用 if 而非 while 檢查條件虛假喚醒導致邏輯錯誤標準模式:while (!condition) wait();
notify() 導致信號遺失多執行緒等待同一條件時,隨機喚醒可能非目標執行緒改用 notifyAll() 或 java.util.concurrent 機制
忽略 InterruptedException吞掉中斷信號,導致執行緒無法優雅關閉重新設置中斷旗標或拋出
鎖持有時間過長在同步區塊執行耗時操作(sleep、I/O),降低並發度縮小同步範圍,僅保護共享狀態讀寫

現代替代方案(強烈建議)

Java 5+ java.util.concurrent 提供更高階、更安全的協作工具:

  • BlockingQueue:ArrayBlockingQueue、LinkedBlockingQueue —— 內建阻塞 put/take,完美替代生產者-消費者
  • Condition:Lock.newCondition() 支援多條件變數、可中斷等待、公平鎖
  • Semaphore/CountDownLatch/CyclicBarrier:特定協作場景
  • CompletableFuture/Parallel Streams:任務並行編排

BlockingQueue 重寫範例

BlockingQueue<Passenger> queue = new LinkedBlockingQueue<>();

// 乘客執行緒
new Thread(() -> {
    while (true) {
        Thread.sleep(3000);
        queue.put(new Passenger()); // 滿則阻塞
        System.out.println("passenger go on");
    }
}).start();

// 司機執行緒
new Thread(() -> {
    while (true) {
        Passenger p = queue.take(); // 空則阻塞
        System.out.println("driver take passenger");
        Thread.sleep(3000); // 開車
        System.out.println("arrived");
    }
}).start();

小結

wait()/notify() 是理解 JVM 監視器機制的基礎,但實務開發極少直接使用。掌握其原理有助於除錯與理解底層庫,生產代碼請優先選用 java.util.concurrent 高階組件,獲得更好的安全性、可讀性與效能。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved