将用一个魔法学院的"信号塔"故事,带你深入理解 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 契约三大法则
- 必须在同步结界内使用(synchronized 块中)
- 调用对象必须与同步锁对象一致
- 等待条件要用循环检查(防止虚假唤醒)
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); // 全部移到竞争队列
}
}
🔄 第三章:信号塔协作流程图
🧪 第四章:魔法学院的实战场景
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 查看:
- 线程状态分布
- 监视器竞争情况
- 等待线程数量
💎 总结:信号塔系统精髓
-
协作机制核心:
wait():释放锁 + 进入等待队列notify():移动一个线程到竞争队列notifyAll():移动所有线程到竞争队列
-
三大原则:
- 始终在同步块中使用
- 始终用循环检查等待条件
- 锁对象与调用对象必须一致
-
底层实现:
- 对象监视器(ObjectMonitor)
- 等待队列(_WaitSet)
- 竞争队列(_EntryList)
-
最佳实践:
- 优先使用
notifyAll()保证安全 - 缩小同步范围减少竞争
- 考虑使用
java.util.concurrent高级工具
- 优先使用
🌟 魔法箴言:
wait/notify = 对象监视器 + 双队列管理 + 条件循环检查
如同魔法学院的信号塔系统,让巫师们在共享资源的协作中保持秩序与高效!
通过这个魔法故事,你不仅学会了正确使用 wait/notify,还深入理解了其底层实现机制。现在,你可以在多线程编程中安全高效地使用这套协作系统了! 🧙♂️🔮