1. 概述

本文将深入探讨 Java 中的 java.lang.IllegalMonitorStateException。这个异常虽然不常直接抛出,但在多线程编程中一旦踩坑,往往让人摸不着头脑。我们会通过一个简单的生产者-消费者模型来复现该异常,分析其成因,并给出正确的修复方式。

核心目标:✅ 理解异常触发机制 ✅ 掌握正确使用 wait() / notify() 的姿势 ❌ 避免低级同步错误

2. 异常触发场景

IllegalMonitorStateException 是 JVM 在多线程同步控制中抛出的一种运行时异常。它明确表示:某个线程试图对一个对象的监视器(monitor)执行 wait()notify()notifyAll() 操作,但该线程并未持有该对象的锁

简单粗暴地说:⚠️ 只要你在 synchronized 块之外调用 wait()notify()notifyAll(),就一定会触发这个异常。

下面我们通过一个典型的“发送-接收”通信模型来复现问题。

2.1 数据载体类

首先定义一个共享数据类 Data,用于在 sender 和 receiver 之间传递消息:

public class Data {
    private String message;

    public void send(String message) {
        this.message = message;
    }

    public String receive() {
        return message;
    }
}

2.2 错误示例:未同步的 Sender

以下 UnsynchronizedSender 类尝试在发送消息后通知接收方,但 notifyAll() 调用未在 synchronized 块中:

class UnsynchronizedSender implements Runnable {
    private static final Logger log = LoggerFactory.getLogger(UnsynchronizedSender.class);
    private final Data data;

    public UnsynchronizedSender(Data data) {
        this.data = data;
    }

    @Override
    public void run() {
        try {
            Thread.sleep(1000);

            data.send("test");

            data.notifyAll(); // ❌ 问题所在:未持有锁就调用 notifyAll
        } catch (InterruptedException e) {
            log.error("thread was interrupted", e);
            Thread.currentThread().interrupt();
        }
    }
}

2.3 错误示例:未同步的 Receiver

同理,UnsynchronizedReceiver 在未获取锁的情况下调用 wait()

public class UnsynchronizedReceiver implements Runnable {
    private static final Logger log = LoggerFactory.getLogger(UnsynchronizedReceiver.class);
    private final Data data;
    private String message;

    public UnsynchronizedReceiver(Data data) {
        this.data = data;
    }

    @Override
    public void run() {
        try {
            data.wait(); // ❌ 问题所在:未持有锁就调用 wait
            this.message = data.receive();
        } catch (InterruptedException e) {
            log.error("thread was interrupted", e);
            Thread.currentThread().interrupt();
        }
    }

    public String getMessage() {
        return message;
    }
}

2.4 测试代码与异常输出

启动两个线程进行通信:

public void sendData() {
    Data data = new Data();

    UnsynchronizedReceiver receiver = new UnsynchronizedReceiver(data);
    Thread receiverThread = new Thread(receiver, "receiver-thread");
    receiverThread.start();

    UnsynchronizedSender sender = new UnsynchronizedSender(data);
    Thread senderThread = new Thread(sender, "sender-thread");
    senderThread.start();

    try {
        senderThread.join(1000);
        receiverThread.join(1000);
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }
}

执行结果会抛出 IllegalMonitorStateException

[sender-thread] ERROR com.example.UnsynchronizedSender - illegal monitor state exception occurred
java.lang.IllegalMonitorStateException: null
    at java.base/java.lang.Object.notifyAll(Native Method)
    at com.example.UnsynchronizedSender.run(UnsynchronizedSender.java:15)
    at java.base/java.lang.Thread.run(Thread.java:844)

[receiver-thread] ERROR com.example.UnsynchronizedReceiver - illegal monitor state exception occurred
java.lang.IllegalMonitorStateException: null
    at java.base/java.lang.Object.wait(Native Method)
    at java.base/java.lang.Object.wait(Object.java:328)
    at com.example.UnsynchronizedReceiver.run(UnsynchronizedReceiver.java:12)
    at java.base/java.lang.Thread.run(Thread.java:844)

3. 正确修复方式

要解决 IllegalMonitorStateException,核心原则只有一条:✅ 所有对 wait()notify()notifyAll() 的调用,必须在 synchronized 块或方法中进行,且同步对象必须与调用这些方法的对象一致

3.1 修复 Sender

使用 synchronized(data) 确保在调用 notifyAll() 前已获取 data 对象的内置锁:

class SynchronizedSender implements Runnable {
    private final Data data;

    public SynchronizedSender(Data data) {
        this.data = data;
    }

    @Override
    public void run() {
        synchronized (data) {
            data.send("test");
            data.notifyAll(); // ✅ 此时已持有 data 的锁
        }
    }
}

3.2 修复 Receiver

同样地,wait() 调用也必须包裹在 synchronized 块中:

class SynchronizedReceiver implements Runnable {
    private static final Logger log = LoggerFactory.getLogger(SynchronizedReceiver.class);
    private final Data data;
    private String message;

    public SynchronizedReceiver(Data data) {
        this.data = data;
    }

    @Override
    public void run() {
        synchronized (data) {
            try {
                data.wait(); // ✅ 此时已持有 data 的锁
                this.message = data.receive();
            } catch (InterruptedException e) {
                log.error("thread was interrupted", e);
                Thread.currentThread().interrupt();
            }
        }
    }

    public String getMessage() {
        return message;
    }
}

3.3 验证修复效果

使用修复后的类重新运行测试,线程间通信正常完成,不再抛出异常。

⚠️ 注意:实际生产中还需考虑 wait() 被虚假唤醒(spurious wakeup)的情况,建议使用 while 循环检查条件而非 if。本文为简化示例未展开。

4. 总结

  • IllegalMonitorStateException 的根本原因是:在未持有对象锁的情况下调用 wait() / notify() / notifyAll()
  • 修复方案简单直接:✅ 所有相关调用必须置于 synchronized 块中,且同步对象与方法调用对象一致
  • 虽然现代并发工具类(如 BlockingQueueCountDownLatch)已减少对原始 wait/notify 的依赖,但理解其原理仍是 Java 多线程的必修课

示例代码已托管至 GitHub:https://github.com/java-concurrency-tutorial/wait-notify-example


原始标题:IllegalMonitorStateException in Java