IllegalMonitorStateException 的核心是线程未持有对象锁就调用 wait/notify/notifyAll:JVM 通过 monitor 检查锁归属,不满足则抛异常;必须在 synchronized 块内调用以保障状态修改与通知的原子性及可见性。

IllegalMonitorStateException 的核心逻辑非常直接:Java 要求 wait()、notify()、notifyAll() 这三个方法必须由当前对象的锁持有者调用。线程没拿到锁就去发号施令,JVM 就会拒绝执行并抛出这个异常。
为什么必须持有锁才能调用?
这不是语法限制,而是语义保障:
- wait() 不仅让线程挂起,还会主动释放当前持有的对象锁,以便其他线程能进入同步块修改共享状态;没有锁可释放,行为就失去意义,甚至引发竞态。
- notify()/notifyAll() 是“通知等待者条件可能已变”,这个动作本身要和共享状态的修改构成原子操作——通常修改完状态后立刻通知,所以它们必须出现在同一个 synchronized 块中,确保通知发生时状态更新已完成且可见。
- JVM 通过监视器(monitor)跟踪每个对象的锁归属,调用这三个方法前会检查当前线程是否在该对象的 monitor owner 列表里,不在就立即 throw。
常见触发场景
以下写法都会直接触发 IllegalMonitorStateException:
- 对一个普通对象调用 wait(),但没用 synchronized(lock) 包裹
- synchronized(obj1) { obj2.wait(); } —— 锁的是 obj1,等的是 obj2,锁对象和等待对象不一致
- 在 ReentrantLock.lock() 后调用 obj.wait() —— 显式锁和内置锁机制互不兼容,不能混用
- 在 synchronized 方法内调用 this.wait() 是合法的,但若方法被重写或代理,实际 this 可能不是预期锁对象,需格外留意
如何验证是否持有锁?
没有运行时 API 能直接查询“当前线程是否持有某对象锁”,但可通过以下方式间接确认:
- 确保所有 wait/notify 调用都严格处于
synchronized(目标对象)块内部 - 避免跨对象操作:synchronized(A) 内只调用 A.wait() / A.notify(),不碰 B 或 this
- 使用 jstack 查看线程堆栈,确认调用点位于 synchronized 块内(会显示 locked )
修复的关键不是捕获异常,而是预防
这个异常属于编程错误,不是运行时意外,因此:
- 不应该用 try-catch 捕获并“吞掉”它,那只是掩盖问题
- 应该把 wait/notify 的调用位置视为“临界区入口”,和共享变量读写一样重视同步一致性
- 优先考虑用 JUC 工具替代:比如用 BlockingQueue 替代手动 wait/notify 实现生产者-消费者,用 Condition 配合 ReentrantLock 实现更灵活的等待集合

















