wait()必须在synchronized块中调用,因为其本质是原子性地释放锁并进入等待队列,JVM要求调用线程必须是锁的持有者(owner),否则抛出IllegalMonitorStateException。

因为 wait() 会释放当前线程持有的锁,而只有在已持有锁的前提下,JVM 才能安全地执行释放操作;否则会出现“没有锁却想释放”的逻辑错误,直接抛出 IllegalMonitorStateException。
wait 的本质是“释放锁 + 进入等待队列”
调用 wait() 不只是让线程暂停,它必须完成两个原子动作:
- 释放当前对象的 monitor 锁(即 synchronized 锁)
- 将当前线程挂起,并加入该对象的等待队列(wait set)
这两个动作必须由同一个锁来保障其一致性。如果线程没拿到锁就调用 wait(),JVM 根本不知道该释放哪个锁,也无法保证线程状态和锁状态同步,所以强制要求必须先获得锁。
避免竞态条件和虚假唤醒的协同机制
wait() 总是和 notify()/notifyAll() 配合使用,而后者也必须在 synchronized 块中调用。这种设计确保了:
- 检查条件、决定是否 wait 的逻辑不会被其他线程打断(靠锁保护共享状态)
- 唤醒操作和等待操作作用于同一个对象的监视器,避免信号丢失
- 从
wait()返回后,能重新安全地检查条件(因为再次持有了锁)
例如:一个典型的生产者-消费者场景中,消费者在 while 循环里检查缓冲区是否为空,若为空才 wait() —— 这个 while 判断和 wait() 必须在同一个锁下,否则可能刚判断完缓冲区为空,另一个线程就填入数据并 notify,而当前线程还没 wait 就错过了信号。
Java 规范与 JVM 实现层面的硬性约束
JVM 对每个对象维护一个 monitor 结构,其中包含:
- owner 字段(记录当前持有锁的线程)
- EntryList(竞争锁的线程队列)
- WaitSet(调用 wait 后挂起的线程队列)
只有 owner 线程才能把自身从 owner 移出、放入 WaitSet。这不是语言层面的约定,而是 JVM 指令(如 monitorenter/monitorexit 和 wait 指令)的底层契约。脱离 synchronized 调用 wait(),相当于让一个非 owner 线程执行 owner 才能做的操作,JVM 直接拒绝。
不复杂但容易忽略:synchronized 是 wait 的“入场券”,不是可选装饰。没它,wait 就没有上下文,也没有安全保障。










