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

Java 多線程通信深度解析:wait/notify 機制與生產者消費者模式實戰

     編輯:Java 綜合教學

前言

多線程環境下,線程間如何協作、共享資料且保證線程安全,是 Java 並發編程的核心課題。本文從「為何出現安全問題」切入,層層遞進講解 synchronized、wait/notify、wait/notifyAll 的運作原理,並透過「生產者-消費者」經典模型展示最佳實踐代碼。

一、線程安全問題的三要素

  1. 多線程:至少兩個線程並發執行。
  2. 共享資源:多線程操作同一個對象/變數。
  3. 非原子操作:對共享資源的操作包含多條指令(如 i++ 實為讀-改-寫三步)。

缺一不可。單線程或無共享資源皆無安全隱患。

二、synchronized 的局限:同步 ≠ 協作

synchronized 只能保證互斥訪問,無法控制線程執行順序。線程釋放鎖後仍參與下一輪搶佔,可能導致「同步中的不同步」:生產者連續生產、消費者無法及時消費,或反之。

三、等待喚醒機制:Object 級別的協作原語

方法所屬類調用前提行為
wait()Object持有該對象鎖(在 synchronized 內)釋放鎖,進入等待集合,阻塞直到被喚醒或超時
notify()Object持有該對象鎖隨機喚醒一個在該對象上等待的線程
notifyAll()Object持有該對象鎖喚醒所有在該對象上等待的線程

核心規則:等待與喚醒必須針對同一把鎖,否則會拋出 IllegalMonitorStateException。

四、經典模型:生產者-消費者(雙線程交替打印)

版本 1:基礎版(共享對象 + 旗標)

class Resource {
    String name, sex;
    boolean flag = false; // true=已生產,待消費
}

class Input implements Runnable {
    private Resource r;
    Input(Resource r) { this.r = r; }
    public void run() {
        int x = 0;
        while (true) {
            synchronized (r) {
                if (r.flag) {
                    try { r.wait(); } catch (InterruptedException e) {}
                }
                if (x == 0) { r.name = "mike"; r.sex = "man"; }
                else { r.name = "麗麗"; r.sex = "女"; }
                x = (x + 1) % 2;
                r.flag = true;
                r.notify(); // 喚醒消費者
            }
        }
    }
}

class Output implements Runnable {
    private Resource r;
    Output(Resource r) { this.r = r; }
    public void run() {
        while (true) {
            synchronized (r) {
                if (!r.flag) {
                    try { r.wait(); } catch (InterruptedException e) {}
                }
                System.out.println(r.name + "...." + r.sex);
                r.flag = false;
                r.notify(); // 喚醒生產者
            }
        }
    }
}

版本 2:封裝優化版(鎖內聚、職責分離)

class Resource {
    private String name, sex;
    private boolean flag = false;

    public synchronized void set(String name, String sex) {
        if (flag) { try { wait(); } catch (InterruptedException e) {} }
        this.name = name; this.sex = sex;
        flag = true;
        notify();
    }

    public synchronized void out() {
        if (!flag) { try { wait(); } catch (InterruptedException e) {} }
        System.out.println(name + "........" + sex);
        flag = false;
        notify();
    }
}

class Input implements Runnable {
    private Resource r; int x = 0;
    Input(Resource r) { this.r = r; }
    public void run() {
        while (true) {
            if (x == 0) r.set("mike", "man");
            else r.set("麗麗", "女");
            x = (x + 1) % 2;
        }
    }
}

class Output implements Runnable {
    private Resource r;
    Output(Resource r) { this.r = r; }
    public void run() { while (true) r.out(); }
}

優化點:同步邏輯內聚於 Resource,業務線程僅呼叫高層方法,降低耦合、提升可讀性。

五、進階補充:notify vs notifyAll

  • notify():喚醒單一線程,效率高但需確保喚醒的是「正確角色」(如只有消費者在等)。若生產者也在等,可能喚醒錯誤線程導致活鎖。
  • notifyAll():喚醒所有等待線程,配合 while (condition) wait() 循環檢查條件,雖稍耗效能但更安全、通用,推薦生產環境使用。

六、最佳實踐清單

  1. 永遠在 while (條件) wait() 循環中調用 wait(),防止虛假喚醒。
  2. 共享對象字段 private,提供同步方法封裝協作邏輯。
  3. 優先使用 notifyAll(),除非極度確定等待集合單一角色。
  4. 高併發場景考慮 java.util.concurrent 套件(BlockingQueue、Semaphore、Condition)替代手工 wait/notify。

總結

wait/notify 是 Java 線程協作的基石,理解「鎖即條件、條件即鎖」的設計哲學,配合生產者-消費者模型,即可構建正確高效的多線程協作系統。實戰中建議優先採用 J.U.C 高階組件,降低底層同步錯誤風險。

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