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

Java wait/notify 經典協作模式:交替打印 1-100 的完整實現與核心原理解析

     編輯:Java 相關知識

Object.wait()/notify() 是 Java 內置的線程協作機制,基於對象監視器鎖實現條件等待與通知。本文通過一個經典案例——兩線程交替打印 1-100,每線程每次打印 10 個數字,詳細講解鎖關聯、wait/notify 流程、代碼實現細節及常見錯誤避坑指南。

一、核心原理:對象鎖與等待/通知機制

  • 每個 Java 對象關聯一把內置鎖(Monitor)與一個等待隊列。
  • synchronized(obj) 獲取鎖,進入臨界區。
  • 持有鎖時調用 obj.wait():**釋放鎖**,當前線程進入對象的等待隊列阻塞。
  • 持有鎖時調用 obj.notify():**喚醒等待隊列中任一線程**(隨機),該線程從 wait() 返回前需重新競爭獲取鎖。
  • notifyAll() 喚醒隊列中**所有**線程,推薦使用以防信號丟失。

二、案例需求與設計

  • 共享對象:DemoThread 實例(實現 Runnable),持有計數器 time。
  • 兩線程:TestThread 實例,名稱 "1" 和 "2",共享同一 DemoThread 任務對象。
  • 協作邏輯:
    1. 線程 "2" 先啟動,進入 synchronized(this) 調用 wait() 釋放鎖等待。
    2. 線程 "1" 啟動,持有鎖進入循環打印 1-10,打印第 10 個時 notify() 喚醒線程 "2",自身 wait() 等待。
    3. 線程 "2" 獲取鎖,打印 11-20,notify() 喚醒線程 "1",自身 wait()。
    4. 循環至 100 結束。

三、完整代碼實現


package jdeveloper.study;

public class DemoThread implements Runnable {
    public DemoThread() {
        TestThread t1 = new TestThread(this, "1");
        TestThread t2 = new TestThread(this, "2");
        t2.start(); // 關鍵:先啟動線程2,使其先進入 wait()
        t1.start();
    }

    public static void main(String[] args) {
        new DemoThread();
    }

    @Override
    public void run() {
        TestThread t = (TestThread) Thread.currentThread();
        try {
            // 線程2先進入等待,線程1直接繼續
            if (!t.getName().equalsIgnoreCase("1")) {
                synchronized (this) {
                    wait();
                }
            }

            while (true) {
                System.out.println("@time in thread " + t.getName() + " = " + t.increaseTime());
                
                // 每10個數字交換控制權
                if (t.getTime() % 10 == 0) {
                    synchronized (this) {
                        System.out.println("****************************************");
                        notify(); // 喚醒另一線程
                        if (t.getTime() == 100) break; // 結束條件
                        wait(); // 釋放鎖並等待
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

class TestThread extends Thread {
    private int time = 0;
    public TestThread(Runnable r, String name) { super(r, name); }
    public int getTime() { return time; }
    public int increaseTime() { return ++time; }
}

四、關鍵細節深度解析

1. 啟動順序決定初始狀態

t2.start() 先於 t1.start(),線程 "2" 率先進入 run(),因名稱非 "1" 進入 synchronized(this){ wait(); } 釋放鎖並阻塞。線程 "1" 隨後啟動,獲取鎖直接進入打印循環。若順序反轉,線程 "1" 會先進入 wait() 導致死等。

2. 同步塊粒度精確控制交接點

同步塊僅包裹 notify()/wait() 及結束判斷,打印與計數在同步塊外執行,最大化併發窗口(雖本例為交替執行,併發度為 1,但原則通用)。

3. 共享計數器與線程身份綁定

TestThread 封裝 time 計數器,每線程實例獨立計數。但因共享同一 DemoThread 任務對象,run() 中通過 Thread.currentThread() 區分線程身份,配合各自計數器實現全局 1-100 有序推進。

4. 結束條件在同步塊內檢查

if (t.getTime() == 100) break; 放在 notify() 之後、wait() 之前,確保最後一輪打印完成後主動退出,不再進入等待,避免另一線程永久阻塞。

五、常見錯誤與避坑指南

錯誤模式後果修正
用 if 代替 while 檢查條件虛假喚醒導致邏輯錯誤永遠用 while(條件不滿足) wait()
不同線程同步於不同鎖對象wait/notify 失效,線程不互通共享同一任務實例,同步於 this 或共享 final Object lock
notify() 後未釋放鎖就執行耗時操作被喚醒線程獲取不到鎖,併發度降為串行notify() 後盡快退出同步塊
缺少啟動順序控制雙方同時 wait() 導致死鎖明確協作協議:一方先 wait,另一方先 notify

六、進階替代方案:JUC 同步器

實際項目中,推薦使用更高級、語義更清晰的工具:

  • ReentrantLock + Condition:支持多條件隊列、可中斷等待、公平鎖。
  • Phaser / CyclicBarrier:多方同步屏障,適合階段性協作。
  • Semaphore:信號量控制資源訪問許可。
  • BlockingQueue:生產消費模式首選,內置阻塞協作。

七、編譯運行指令


javac -d . DemoThread.java
java -cp . jdeveloper.study.DemoThread

掌握 wait/notify 核心協作模式,是理解 Java 併發本質、排查線程協作 Bug、設計高級同步組件的基石。

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