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
块中,且同步对象与方法调用对象一致 - 虽然现代并发工具类(如
BlockingQueue
、CountDownLatch
)已减少对原始wait/notify
的依赖,但理解其原理仍是 Java 多线程的必修课
示例代码已托管至 GitHub:https://github.com/java-concurrency-tutorial/wait-notify-example