为什么 Java 中 wait() 和 notify() 必须在 synchronized 代码块中使用
一、核心结论(官方定义)
Java 中的 Object.wait()、Object.notify() 和 Object.notifyAll() 必须在同步上下文(synchronized 块或方法)中被调用,否则会抛出 java.lang.IllegalMonitorStateException。
这是由 Java 虚拟机的对象监视器(Monitor)机制决定的。
📚 官方文档说明(Java SE 8+):
原文:
This method should only be called by a thread that is the owner of this object’s monitor.
A thread becomes the owner of the object’s monitor in one of three ways:
- By executing a synchronized instance method of that object.
- By executing the body of a synchronized statement that synchronizes on the object.
- For class objects, by executing a synchronized static method of that class.
二、详细技术分析
1. 背景:Java 中的对象监视器 Monitor
每个 Java 对象都可以用作一把锁(Lock),也称为“监视器锁”。
- 当一个线程进入
synchronized(obj)时,它会尝试获取 obj 的 Monitor。 - Monitor 是 JVM 的一种同步机制,是
wait()/notify()等行为的基础。
2. wait()、notify() 的内部原理
wait():- 当前线程必须持有对象的 Monitor。
- 调用后线程会释放锁,并进入等待队列,直到被
notify()唤醒。
notify():- 当前线程也必须持有对象的 Monitor。
- 唤醒等待队列中的一个线程(不会立即执行,需重新竞争锁)。
重点:JVM 规定这些行为只能发生在持有对象锁时,才能保证原子性与可见性。
为什么不加 synchronized 会报错?
如果你写:
Object lock = new Object();
lock.wait(); // ❌ 报错:IllegalMonitorStateException
会抛出异常,因为当前线程没有持有 lock 的 Monitor。
这是 Java 的安全机制,防止线程在未加锁情况下影响共享状态。
三、源码证明:OpenJDK 实现
在 OpenJDK 17 的 JVM_MonitorWait 中可以看到如下代码片段:
JVM_ENTRY(void, JVM_MonitorWait(JNIEnv* env, jobject obj, jlong ms))
...
if (!ObjectMonitor::is_owner(obj, current_thread)) {
THROW(vmSymbols::java_lang_IllegalMonitorStateException());
}
...
JVM_END
说明:如果当前线程不是对象的拥有者(没有进入同步块),直接抛出 IllegalMonitorStateException。
四、替代方案:使用 Lock + Condition 更灵活
java.util.concurrent.locks 提供更灵活的同步机制:
✔ 示例:
import java.util.concurrent.locks.*;
class LockConditionExample {
private final Lock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
public void awaitMethod() throws InterruptedException {
lock.lock();
try {
condition.await(); // 相当于 wait()
} finally {
lock.unlock();
}
}
public void signalMethod() {
lock.lock();
try {
condition.signal(); // 相当于 notify()
} finally {
lock.unlock();
}
}
}
官方文档:
五、官方推荐做法总结
| 方法 | 是否必须同步? | 推荐方式 | 参考实现 |
|---|---|---|---|
wait() / notify() | ✅ 是 | synchronized 块 | synchronized(obj) { ... } |
await() / signal() | ✅ 是 | Lock + Condition | lock.lock() {...} |
六、总结(面试 & 实战角度)
| 问题 | 解答 |
|---|---|
为什么 wait() 必须在同步块中? | 因为只有持有对象监视器的线程才可调用它,否则 JVM 抛异常。 |
| 为什么要这样设计? | 避免竞态条件、确保线程状态一致性、安全地释放/等待锁。 |
| 有替代方案吗? | 有,ReentrantLock + Condition 提供更精细控制(多个条件队列等)。 |
| 如果不用 synchronized 会怎样? | 直接运行时报错:IllegalMonitorStateException,线程无锁调用是不合法的。 |
更多详细内容请关注其他相关文章!