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

Java 多線程核心方法深度解析:wait/notify/notifyAll 與 Object 鎖機制

     編輯:Java 相關知識

核心認知:鎖屬於物件,而非線程

Java 每個物件天生擁有一把監視器鎖。wait()、notify()、notifyAll() 定義在 Object 而非 Thread,因為它們操作的是物件的鎖與等待隊列。

三大方法語意精解

wait() —— 釋放鎖並進入等待

synchronized (obj) {
    // 前置條件:持有 obj 鎖
    obj.wait(); 
    // 1. 釋放 obj 鎖
    // 2. 當前線程進入 obj 的等待集合
    // 3. 線程狀態:WAITING / TIMED_WAITING
    // 4. 被喚醒後:重新競爭 obj 鎖 → 獲得 → 繼續執行
}

notify() —— 隨機喚醒一個

synchronized (obj) {
    obj.notify();
    // 1. 從 obj 等待集合隨機選一線程
    // 2. 該線程從 WAITING → BLOCKED(等待鎖)
    // 3. 當前線程繼續持有鎖,執行後續程式碼
    // 4. 鎖釋放後,被喚醒線程競爭獲得
}

notifyAll() —— 喚醒全部

將等待集合中所有線程移至鎖競爭隊列。適用於等待條件不同、或為避免信號丟失的保守策略。

必須在 synchronized 內呼叫的鐵律

// 錯誤:拋出 IllegalMonitorStateException
obj.wait(); 

// 正確
synchronized (obj) {
    obj.wait();
}

原因:JVM 驗證當前線程是否為鎖持有者,保護等待集合操作的原子性。

經典範式:條件等待迴圈

synchronized (lock) {
    while (!condition) { // 必須用 while,防虛假喚醒
        try {
            lock.wait();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt(); // 保留中斷狀態
            throw new RuntimeException("線程被中斷", e);
        }
    }
    // 條件滿足,安全執行
    doAction();
}

完整實戰:生產者-消費者(ArrayList 版)

共享容器

public class SharedContainer {
    private final List container = new ArrayList<>();
    private final int maxSize = 5;
    
    public synchronized void produce() throws InterruptedException {
        while (container.size() >= maxSize) {
            System.out.println("緩衝區滿,生產者等待...");
            wait();
        }
        container.add(new Object());
        System.out.println("生產第 " + container.size() + " 個");
        notifyAll(); // 喚醒消費者
    }
    
    public synchronized Object consume() throws InterruptedException {
        while (container.isEmpty()) {
            System.out.println("緩衝區空,消費者等待...");
            wait();
        }
        Object item = container.remove(0);
        System.out.println("消費第 " + (container.size() + 1) + " 個,剩餘 " + container.size());
        notifyAll(); // 喚醒生產者
        return item;
    }
}

多執行緒測試

public class ProducerConsumerDemo {
    public static void main(String[] args) {
        SharedContainer container = new SharedContainer();
        
        // 2 生產者 + 2 消費者
        for (int i = 0; i < 2; i++) {
            new Thread(() -> {
                while (true) {
                    try { container.produce(); Thread.sleep(100); } 
                    catch (InterruptedException e) { break; }
                }
            }, "Producer-" + i).start();
            
            new Thread(() -> {
                while (true) {
                    try { container.consume(); Thread.sleep(150); } 
                    catch (InterruptedException e) { break; }
                }
            }, "Consumer-" + i).start();
        }
    }
}

關鍵細節深度剖析

1. wait() 釋放的是「當前物件」的鎖

synchronized (A) {
    synchronized (B) {
        B.wait(); // 僅釋放 B 的鎖,A 的鎖仍持有!
    }
}

這是巢狀同步的巨大陷阱,嚴禁在巢狀 synchronized 中呼叫 wait()。

2. 虛假喚醒真實存在

Linux pthread_cond_wait、Windows ConditionVariable 均可能無信號喚醒。JVM 規範明確允許,必須用 while 迴圈防禦。

3. notify() 可能導致信號丟失

// 兩類消費者:消費 A 和 消費 B
// 生產者 notify() 可能喚醒錯誤類型,導致該類型永不被喚醒
// 解決:notifyAll() 或分離鎖物件

現代替代:Lock + Condition

ReentrantLock lock = new ReentrantLock();
Condition notEmpty = lock.newCondition();
Condition notFull = lock.newCondition();

// 生產者
lock.lock();
try {
    while (queue.size() == capacity) notFull.await();
    queue.add(item);
    notEmpty.signal(); // 精確喚醒消費者
} finally {
    lock.unlock();
}

優勢:一把鎖綁定多條件、支援公平鎖、可中斷等待、超時精確控制。

總結

wait/notify/notifyAll 是 Java 併發基石,但易用錯、難除錯、功能受限。掌握其鎖釋放/重獲語意、虛假喚醒、迴圈檢查、巢狀同步陷阱是進階開發者必修課。實戰中優先使用 BlockingQueue、Lock+Condition、Semaphore 等高層工具,將併發複雜度交給專家實現的並發包。

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