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

Java wait/notify 實現線程協作完整教學:從忙等待到高效通知

     編輯:Java 相關知識

核心 API

方法所屬類別行為
wait()Object釋放鎖、進入等待集合,直到 notify/notifyAll 或超時
notify()Object喚醒單一等待執行緒
notifyAll()Object喚醒所有等待執行緒
sleep()Thread**不釋放鎖**,單純暫停
yield()Thread**不釋放鎖**,讓出 CPU

必須在 synchronized 方法/區塊內呼叫,否則拋出 IllegalMonitorStateException。

範例一:單一執行緒喚醒多個工作者 (Game/Athlete)

public class Game implements Runnable {
    private final Set<Athlete> players = new HashSet<>();
    private boolean start = false;

    public synchronized void prepare(Athlete a) throws InterruptedException {
        System.out.println(a + " ready!");
        while (!start) wait(); // 釋放鎖等待
        System.out.println(a + " go!");
    }

    public synchronized void go() {
        start = true;
        notifyAll(); // 喚醒所有選手
    }

    public void ready() {
        for (Athlete a : players) new Thread(a).start();
    }

    @Override public void run() {
        System.out.println("Ready......");
        ready();
        System.out.println("Go!");
        go();
    }

    public static void main(String[] args) {
        Game game = new Game();
        for (int i = 0; i < 10; i++) game.addPlayer(new Athlete(i, game));
        new Thread(game).start();
    }
}

class Athlete implements Runnable {
    private final int id;
    private final Game game;
    public Athlete(int id, Game game) { this.id = id; this.game = game; }
    @Override public void run() {
        try { game.prepare(this); }
        catch (InterruptedException e) { System.out.println(this + " quit"); }
    }
    @Override public String toString() { return "Athlete<" + id + ">"; }
}

輸出:所有選手先印 "ready!",主執行緒印 "Go!" 後,選手依序印 "go!"。

範例二:忙等待 vs wait/notify

忙等待 (Busy Waiting) - 佔用 CPU

class Monitor implements Runnable {
    private volatile boolean go = false;
    public void gotMessage() { go = true; }
    public void watching() { while (!go); } // 空迴圈燒 CPU
    @Override public void run() { watching(); System.out.println("He has gone."); }
}

wait/notify 改良 - 釋放 CPU

class Monitor implements Runnable {
    private volatile boolean go = false;
    public synchronized void gotMessage() { go = true; notify(); }
    public synchronized void watching() throws InterruptedException {
        while (!go) wait(); // 釋放鎖、進入等待態
    }
    @Override public void run() {
        try { watching(); } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println("He has gone.");
    }
}

關鍵模式:條件變數 (Condition Variable)

synchronized (lock) {
    while (conditionNotMet) {
        lock.wait(); // 1. 釋放鎖 2. 等待 3. 重新獲得鎖
    }
    // 條件滿足,執行關鍵區
}
  • **永遠用 while 而非 if**:防止虛假喚醒。
  • volatile 修飾條件旗標,保證可見性。

現代替代方案 (Java 5+)

常見錯誤與避坑指南

類別優勢
java.util.concurrent.locks.Condition單一鎖支援多條件隊列,API 更豐富 (await(), signal())
BlockingQueue生產者-消費者模式首選,內建阻塞
CountDownLatch / CyclicBarrier / Phaser特定同步場景專用
CompletableFuture / parallelStream非阻塞組合式非同步
錯誤後果修正
非 synchronized 中呼叫 wait/notifyIllegalMonitorStateException加上 synchronized 區塊
用 if 替代 while 檢查條件虛假喚醒導致邏輯錯誤改用 while (condition) wait()
notify() 單一喚醒但有多種條件喚醒錯誤執行緒,造成活鎖改用 notifyAll() 或 Condition
鎖物件不一致wait/notify 無效確保同一物件實例

結語

wait/notify 是 Java 線程協作的基石,理解「釋放鎖→等待→重獲鎖」狀態機與「while 迴圈防虛假喚醒」模式至關重要。現代開發優先使用 java.util.concurrent 高層抽象,但掌握底層機制有助於除錯與理解框架原理。

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