synchronized&wait¬ify
1. 理解 Monitor 监视器模型
在 JVM 中,每个对象都关联着一个监视器,如下图所示。
一个对象实例,拥有头对象信息,里面有 Mark Word,内部有个 monitor 指针,指向外部的 ObjectMonitor 对象(c++编写的),Class Metadata Address 指针,指向类信息对象。

ObjectMonitor 对象 有两个两个关键的队列:
- 入口队列 (Entry Set): 所有试图进入synchronized 同步代码块的线程,如果发现对象的锁已经被占用了,则进入这个队列进行等待。锁被释放的时候,他们会进行竞争。
- 等待队列 (Wait Set): 当已经获得锁的线程发现执行条件不满足的,他会调用 wait () 方法。这时,该线程会释放锁,并进入这个对象的等待队列,暂停执行。
2. 等待通知示例代码
public class ProducerConsumer {
private final Object lock = new Object(); // 共享锁对象
private int count = 0; // 共享资源
private final int LIMIT = 5; // 资源上限
// 生产者方法
public void produce() throws InterruptedException {
synchronized (lock) {
// 必须用while循环检查条件
while (count >= LIMIT) {
System.out.println("缓冲区已满,生产者等待...");
lock.wait(); // 1. 释放lock锁 2. 进入等待 3. 被唤醒后从这返回
}
// 条件满足,生产数据
count++;
System.out.println("生产后,当前数量: " + count);
// 生产完成后,通知消费者(唤醒一个等待的消费者线程)
lock.notify();
// lock.notifyAll(); // 在多生产者多消费者场景下更安全
}
}
// 消费者方法
public void consume() throws InterruptedException {
synchronized (lock) {
// 必须用while循环检查条件
while (count <= 0) {
System.out.println("缓冲区为空,消费者等待...");
lock.wait(); // 1. 释放lock锁 2. 进入等待 3. 被唤醒后从这返回
}
// 条件满足,消费数据
count--;
System.out.println("消费后,当前数量: " + count);
// 消费完成后,通知生产者(唤醒一个等待的生产者线程)
lock.notify();
// lock.notifyAll(); // 在多生产者多消费者场景下更安全
}
}
}
3. 关键注意事项与最近实践
- 必须在同步块中调用:这是硬性规定。
wait()和notify()的调用必须位于synchronized代码块或方法内,且调用它们的对象必须与synchronized锁定的对象是同一个,否则会抛出 `IllegalMonitorStateException - 始终在循环中检查条件:这是防御虚假唤醒的最佳实践。虚假唤醒是指线程在没有收到明确通知的情况下被唤醒。使用
if判断可能会导致条件未满足就继续执行,从而引发逻辑错误 - notify() 与 notifyAll() 的选择:
notify()随机唤醒等待队列中的一个线程,效率较高,但风险在于可能唤醒的不是当前所需条件的线程,导致“信号丢失”或某些线程长期“饥饿”notifyAll()会唤醒所有等待的线程,让它们都去竞争锁并重新检查条件。这更安全,但会带来一定的性能开销。在复杂的多条件等待场景下,通常更推荐使用notifyAll()
- 考虑使用 java. util. concurrent 工具:对于复杂的并发场景,Java 提供的
java.util.concurrent包下的高级工具(如ReentrantLock及其关联的Condition对象)能提供更灵活、更强大的线程通信能力,例如支持多个等待条件队列