🔮 wait/notify/notifyAll:魔法学院的信号塔系统

94 阅读5分钟

将用一个魔法学院的"信号塔"故事,带你深入理解 Java 中 wait()notify(), 和 notifyAll() 的底层原理和使用方法。在这个魔法世界里,巫师(线程)们通过信号塔(对象监视器)进行高效协作!

🏰 故事背景:霍格沃茨协作系统

想象魔法学院中有:

  • 巫师学徒 = 线程(Thread)
  • 魔法水晶球 = 共享对象(Object)
  • 信号塔 = 对象监视器(Monitor)
  • 协作契约 = wait()/notify() 机制

巫师们通过水晶球(共享资源)通信,但需要信号塔系统协调,避免混乱!


🔮 第一章:信号塔的契约(基础用法)

1.1 核心魔法咒语

java

public final void wait() throws InterruptedException;
public final void notify();
public final void notifyAll();

1.2 契约三大法则

  1. 必须在同步结界内使用(synchronized 块中)
  2. 调用对象必须与同步锁对象一致
  3. 等待条件要用循环检查(防止虚假唤醒)

1.3 基础协作模型

java

class MagicSignalTower {
    private boolean signalReceived = false;
    
    // 等待信号的巫师
    public synchronized void waitForSignal() throws InterruptedException {
        while (!signalReceived) { // 循环检查条件
            wait(); // 释放锁并等待
        }
        System.out.println("🔮 收到信号,开始施法!");
    }
    
    // 发送信号的巫师
    public synchronized void sendSignal() {
        signalReceived = true;
        notify(); // 唤醒一个等待巫师
        // notifyAll(); // 唤醒所有等待巫师
    }
}

⚙️ 第二章:信号塔的运作原理(源码级解析)

2.1 对象头中的秘密

每个 Java 对象都有一个隐藏的监视器(Monitor)

// hotspot/src/share/vm/runtime/objectMonitor.hpp
class ObjectMonitor {
    volatile markOop   _header;      // 对象头备份
    void*     volatile _object;      // 关联的对象
    volatile intptr_t  _count;       // 重入次数
    Thread*  volatile _owner;        // 当前持有锁的线程
    ObjectWaiter* volatile _WaitSet; // 等待队列(调用wait的线程)
    ObjectWaiter* volatile _EntryList; // 竞争队列
};

2.2 wait() 的魔法流程

当巫师调用 obj.wait()

java

public final void wait() throws InterruptedException {
    wait(0); // 调用本地方法
}

// 本地方法实现 (ObjectMonitor::wait)
void ObjectMonitor::wait(jlong millis, bool interruptible, TRAPS) {
    // 1. 检查当前线程是否持有锁
    if (THREAD != _owner) {
        THROW_MSG(vmSymbols::java_lang_IllegalMonitorStateException(), 
                 "current thread not owner");
    }
    
    // 2. 创建等待节点加入_WaitSet
    ObjectWaiter node(THREAD);
    node.TState = ObjectWaiter::TS_WAIT;
    AddWaiter(&node); // 添加到等待队列
    
    // 3. 释放锁(让其他巫师可以进入)
    exit(true, Self);
    
    // 4. 阻塞当前线程(进入WAITING状态)
    Self->_ParkEvent->park(millis);
    
    // 5. 被唤醒后重新竞争锁
    enter(Self);
}

2.3 notify() 的唤醒机制

java

// ObjectMonitor::notify
void ObjectMonitor::notify(TRAPS) {
    if (THREAD != _owner) { // 检查锁持有
        throw IllegalMonitorStateException();
    }
    
    // 从_WaitSet取出第一个等待者
    ObjectWaiter* iterator = DequeueWaiter();
    if (iterator != null) {
        // 移动到_EntryList(竞争队列)
        iterator->TState = ObjectWaiter::TS_ENTER;
        Enqueue(iterator);
    }
}

2.4 notifyAll() 的广播机制

java

void ObjectMonitor::notifyAll(TRAPS) {
    // 遍历整个_WaitSet
    ObjectWaiter* iterator;
    while ((iterator = DequeueWaiter()) != null) {
        iterator->TState = ObjectWaiter::TS_ENTER;
        Enqueue(iterator); // 全部移到竞争队列
    }
}

🔄 第三章:信号塔协作流程图

deepseek_mermaid_20250626_100036.png


🧪 第四章:魔法学院的实战场景

4.1 场景1:魔药酿造协作

java

class PotionBrewing {
    private boolean ingredientsReady = false;
    
    // 学徒:准备材料
    public synchronized void prepareIngredients() {
        System.out.println("🧪 准备魔药材料...");
        ingredientsReady = true;
        notifyAll(); // 通知所有等待的巫师
    }
    
    // 教授:等待材料
    public synchronized void brewPotion() throws InterruptedException {
        while (!ingredientsReady) {
            System.out.println("⌛ 教授等待材料...");
            wait(); // 释放锁等待
        }
        System.out.println("✨ 开始酿造顶级魔药!");
    }
}

// 使用示例
PotionBrewing cauldron = new PotionBrewing();

Thread professor = new Thread(() -> {
    try { cauldron.brewPotion(); } 
    catch (InterruptedException e) {}
});

Thread apprentice = new Thread(() -> {
    cauldron.prepareIngredients();
});

professor.start();
Thread.sleep(500); // 确保教授先等待
apprentice.start();

4.2 场景2:有限资源分配

java

class MagicLibrary {
    private int availableBooks = 3;
    private final Object lock = new Object();
    
    public void borrowBook(String wizard) throws InterruptedException {
        synchronized(lock) {
            // 循环检查资源可用性
            while (availableBooks == 0) {
                System.out.println(wizard + "等待书籍...");
                lock.wait();
            }
            availableBooks--;
            System.out.println(wizard + "借到书!剩余:" + availableBooks);
        }
    }
    
    public void returnBook(String wizard) {
        synchronized(lock) {
            availableBooks++;
            System.out.println(wizard + "归还书!剩余:" + availableBooks);
            lock.notify(); // 通知一个等待者
        }
    }
}

⚠️ 第五章:信号塔使用禁忌

5.1 常见错误1:未持有锁调用

java

public void brokenWait() {
    try {
        wait(); // ❌ 抛出IllegalMonitorStateException
    } catch (InterruptedException e) {}
}

5.2 常见错误2:使用if检查条件

java

public synchronized void riskyWait() throws InterruptedException {
    if (!condition) { // ❌ 应该用while
        wait();
    }
    // 被唤醒后可能条件仍不满足!
}

5.3 常见错误3:错误的对象锁定

java

private final Object lock1 = new Object();
private final Object lock2 = new Object();

public void wrongLock() throws InterruptedException {
    synchronized(lock1) {
        lock2.wait(); // ❌ 锁对象与wait对象不一致
    }
}

🌟 第六章:高级协作模式

6.1 多条件等待

java

class AdvancedTower {
    private boolean signalA = false;
    private boolean signalB = false;
    private final Object lock = new Object();
    
    public void waitForA() throws InterruptedException {
        synchronized(lock) {
            while (!signalA) lock.wait();
            System.out.println("收到A信号!");
        }
    }
    
    public void waitForB() throws InterruptedException {
        synchronized(lock) {
            while (!signalB) lock.wait();
            System.out.println("收到B信号!");
        }
    }
    
    public void sendSignalA() {
        synchronized(lock) {
            signalA = true;
            lock.notifyAll(); // 必须用notifyAll
        }
    }
    
    public void sendSignalB() {
        synchronized(lock) {
            signalB = true;
            lock.notifyAll(); // 必须用notifyAll
        }
    }
}

6.2 超时等待

java

public synchronized void timedWait() throws InterruptedException {
    long start = System.currentTimeMillis();
    long timeout = 5000; // 5秒超时
    
    while (!condition) {
        long remaining = timeout - (System.currentTimeMillis() - start);
        if (remaining <= 0) {
            System.out.println("等待超时!");
            return;
        }
        wait(remaining); // 带超时的等待
    }
    // 条件满足后的操作
}

⚡ 第七章:信号塔性能优化

7.1 减少锁竞争

java

// 错误:大范围同步
public synchronized void process() {
    // 耗时操作1
    wait(); // 阻塞期间其他线程无法进入
    // 耗时操作2
}

// 正确:缩小同步范围
public void optimizedProcess() {
    synchronized(this) {
        while (!condition) wait();
    }
    // 其他非同步操作
}

7.2 使用 notify() 替代 notifyAll()

java

public synchronized void efficientNotify() {
    // 当只有一个线程可被唤醒时
    if (shouldNotifySingle()) {
        notify(); // 减少不必要的唤醒
    } else {
        notifyAll();
    }
}

7.3 避免嵌套监视器锁死

java

// 危险:嵌套监视器
class NestedMonitor {
    private final Object lock1 = new Object();
    private final Object lock2 = new Object();
    
    public void problemMethod() throws InterruptedException {
        synchronized(lock1) {
            synchronized(lock2) {
                lock2.wait(); // ❌ 只释放lock2,lock1仍被持有
            }
        }
    }
}

🔍 第八章:调试信号塔系统

8.1 线程转储分析

bash

$ jstack <pid>

查看等待状态:

text

"Wizard-1" #12 prio=5 os_prio=0 tid=0x00007f... nid=0x7e9c 
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x000000076ab62d98> (a MagicSignalTower)
        at MagicSignalTower.waitForSignal(MagicSignalTower.java:10)
        - locked <0x000000076ab62d98> (a MagicSignalTower)

8.2 可视化监控

使用 VisualVM 或 JConsole 查看:

  • 线程状态分布
  • 监视器竞争情况
  • 等待线程数量

💎 总结:信号塔系统精髓

  1. 协作机制核心

    • wait():释放锁 + 进入等待队列
    • notify():移动一个线程到竞争队列
    • notifyAll():移动所有线程到竞争队列
  2. 三大原则

    • 始终在同步块中使用
    • 始终用循环检查等待条件
    • 锁对象与调用对象必须一致
  3. 底层实现

    • 对象监视器(ObjectMonitor)
    • 等待队列(_WaitSet)
    • 竞争队列(_EntryList)
  4. 最佳实践

    • 优先使用 notifyAll() 保证安全
    • 缩小同步范围减少竞争
    • 考虑使用 java.util.concurrent 高级工具

🌟 魔法箴言
wait/notify = 对象监视器 + 双队列管理 + 条件循环检查
如同魔法学院的信号塔系统,让巫师们在共享资源的协作中保持秩序与高效!

通过这个魔法故事,你不仅学会了正确使用 wait/notify,还深入理解了其底层实现机制。现在,你可以在多线程编程中安全高效地使用这套协作系统了! 🧙‍♂️🔮