You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

修改Boolean锁对象抛出IllegalMonitorStateException的原因是什么?

问题原因分析

为什么修改flag后会抛出IllegalMonitorStateException

这个异常的触发规则非常明确:调用wait()/notify()/notifyAll()方法的对象,必须是当前线程已经持有的监视器锁对象,否则就会直接抛出该异常。

你理解的「锁仍是同一个对象」是完全错误的,问题核心出在两点:

  1. Boolean是不可变类,没有任何方法可以修改它内部存储的布尔值,你写的flag = false不是修改原有对象的属性,而是触发自动装箱,等效于执行flag = Boolean.valueOf(false),会让flag引用指向全新的对象。
  2. 代码的执行时序刚好踩中了引用替换的坑:
    • 初始化阶段flag = new Boolean(true),我们记这个初始对象为对象A
    • t0线程启动后先拿到对象A的锁,进入synchronized块,开始sleep 2秒(注意Thread.sleep不会释放锁)
    • 主线程sleep 200ms后启动t1,t1直接执行flag = false,把flag的引用指向了Boolean类的静态常量Boolean.FALSE,我们记为对象B
    • t0睡完2秒后执行flag.wait(),此时flag已经指向对象B,但t0持有的锁是对象A的,没有持有对象B的锁,调用对象B的wait()方法完全不符合规则,自然抛出异常。

为什么把修改操作放到synchronized块里就不抛出异常

这个现象的核心是同步块改变了两个线程的执行时序:
如果你把t1的代码改成如下结构:

public void run() {
    System.out.println("thread 1  run");
    synchronized (flag) {
        flag = false;
        flag.notify();
    }
}

执行时序就变成了:

  1. t0先拿到对象A的锁进入synchronized块,开始sleep,此时t1要进入synchronized(flag)必须等t0释放对象A的锁,进入阻塞状态
  2. t0睡完2秒后执行flag.wait(),此时flag还没被修改,仍然指向对象A,t0持有对象A的锁,调用wait()完全合法,不会抛异常,同时调用wait()后t0会释放对象A的锁,进入等待队列
  3. t1拿到对象A的锁进入同步块,修改flag指向对象B,再调用notify唤醒t0
  4. t0被唤醒后重新拿到对象A的锁,此时判断while(flag)发现flag已经是false,直接退出循环,不会再执行wait(),自然不会触发异常。

最佳实践建议

  • 绝对不要用不可变类(Boolean、Integer、String等)作为锁对象,自动装箱、常量池缓存的特性很容易导致锁对象被意外替换
  • 锁对象应该单独声明,用final修饰防止被修改,例如private final Object lock = new Object();
  • 锁对象和业务状态标志变量要拆分,不要让同一个对象兼任两个角色。

内容的提问来源于stack exchange,提问作者MrRobot9

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.12 16:21:04