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

Java 線程通信核心:wait/notify/notifyAll 深度解析與生產消費者實戰

     編輯:Java 程式設計入門

核心前提:鎖與條件隊列綁定

  • 任意 Java 對象既是內建鎖,也是內建條件隊列
  • wait()、notify()、notifyAll() 是條件隊列的固有方法
  • 調用前必須持有該對象的鎖(進入 synchronized 區塊)
  • 鎖保護的狀態變量與條件隊列綁定,保證「檢查條件 → 等待/通知」的原子性

為什麼必須持有鎖?

防止競態條件:

// 生產者邏輯
if (buffer.full()) wait(); // 1.1 檢查條件 → 1.2 進入等待

// 消費者邏輯
buffer.take();             // 2.1 消費
buffer.notFull();          // 2.2 狀態變更
notifyAll();               // 2.3 通知

無鎖時,可能發生:1.1 → 2.1 → 2.2 → 2.3 → 1.2,導致生產者永久等待(通知丟失)。
synchronized 保證「檢查-等待」與「修改-通知」互斥執行。

API 契約速查

方法語義鎖行為拋出異常
wait()釋放鎖、進入等待集、阻塞直到被喚醒/中斷/超時釋放當前對象鎖IllegalMonitorStateException (無鎖)、InterruptedException
wait(long timeout)帶超時等待同上同上
notify()隨機喚醒一個等待線程不釋放鎖,喚醒線程需重新競爭鎖IllegalMonitorStateException
notifyAll()喚醒所有等待線程同上同上

經典實戰:單緩衝區生產者-消費者

public class Buffer {
    private int value;
    private boolean hasValue = false;

    public synchronized void put(int value) {
        while (hasValue) { // while 防虛假喚醒
            try { wait(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
        }
        this.value = value;
        hasValue = true;
        System.out.println("生產: " + value);
        notifyAll(); // 喚醒消費者
    }

    public synchronized int take() {
        while (!hasValue) {
            try { wait(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
        }
        hasValue = false;
        System.out.println("消費: " + value);
        notifyAll(); // 喚醒生產者
        return value;
    }
}

public class Producer implements Runnable {
    private Buffer buffer;
    public Producer(Buffer buffer) { this.buffer = buffer; }
    @Override public void run() {
        for (int i = 0; i < 10; i++) buffer.put(i);
    }
}

public class Consumer implements Runnable {
    private Buffer buffer;
    public Consumer(Buffer buffer) { this.buffer = buffer; }
    @Override public void run() {
        for (int i = 0; i < 10; i++) buffer.take();
    }
}

public class Main {
    public static void main(String[] args) {
        Buffer buffer = new Buffer();
        new Thread(new Producer(buffer), "生產者").start();
        new Thread(new Consumer(buffer), "消費者").start();
    }
}

進階題:兩線程交替輸出 1,2,1,2...


public class AlternatePrinter implements Runnable {
    private int number;
    private Object lock;
    private volatile int turn; // 0: 線程1, 1: 線程2

    public AlternatePrinter(int number, Object lock, int turn) {
        this.number = number;
        this.lock = lock;
        this.turn = turn;
    }

    @Override
    public void run() {
        synchronized (lock) {
            for (int i = 0; i < 5; i++) {
                while (turn != (number == 1 ? 0 : 1)) {
                    try { lock.wait(); } catch (InterruptedException e) { return; }
                }
                System.out.println(Thread.currentThread().getName() + ": " + number);
                turn = 1 - turn; // 切換輪次
                lock.notifyAll();
            }
        }
    }

    public static void main(String[] args) {
        Object lock = new Object();
        new Thread(new AlternatePrinter(1, lock, 0), "T1").start();
        new Thread(new AlternatePrinter(2, lock, 1), "T2").start();
    }
}

最佳實踐建議

  1. 永遠在 while 迴圈中調用 wait(),防虛假喚醒
  2. 優先使用 notifyAll(),避免單喚醒導致信號丟失(除非確定單一條件)
  3. 高併發場景改用 java.util.concurrent:
    BlockingQueue、CountDownLatch、CyclicBarrier、Semaphore、Lock + Condition
  4. wait/notify 僅用於底層框架構建或極簡場景,業務代碼盡量避免
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved