基于ReentrantLock实现的生产者-消费者缓冲区出现java.lang.IllegalMonitorStateException异常的问题排查
排查Java生产者-缓冲区实现中的IllegalMonitorStateException异常
这个异常我太熟了,咱们一步步拆解问题,再给出解决办法:
异常原因分析
首先得明确:java.lang.IllegalMonitorStateException的核心触发条件是调用Condition的await()/signal()方法时,当前线程没有持有该Condition关联的Lock锁。
看你写的Publisher代码:
- 你从Buffer里直接拿到了
notFull这个Condition对象 - 在
run()方法的循环里,直接调用notFull.await(),但此时你的Publisher线程根本没有获取Buffer中那个ReentrantLock锁! - 更关键的是,你在Publisher里重复实现了Buffer已经做的逻辑——Buffer的
put()方法内部已经通过while(isFull()) notFull.await()处理了缓冲区满的等待,外部完全没必要再重复判断。
这种直接操作外部Condition且不持有对应锁的行为,必然触发IllegalMonitorStateException。
解决方法
第一步:精简Publisher类,移除所有Condition相关代码
把Publisher里的notFull、notEmpty成员变量,以及构造方法中获取它们的逻辑全部删掉,因为这些操作完全没必要,还会引入错误。
第二步:简化run方法,直接调用Buffer的put()
Buffer的put()方法已经封装了缓冲区满时的等待逻辑,外部调用方只需要调用这个方法即可,不需要自己处理锁和Condition。
修改后的完整Publisher代码:
import java.util.Random; import java.util.concurrent.atomic.AtomicLong; public class Publisher implements Runnable { private static AtomicLong aLong = new AtomicLong(0); private long instance_id; private Buffer buffer; public Publisher(Buffer buffer) { this.instance_id = aLong.getAndIncrement(); this.buffer = buffer; } @Override public void run() { String threadName = "Publisher_Thread_" + this.instance_id; Thread.currentThread().setName(threadName); Random random = new Random(); while (true) { try { int item = random.nextInt(1, 10); this.buffer.put(item); // 直接调用put,内部已处理等待 System.out.println(threadName + " added " + item); Thread.sleep(random.nextInt(1000, 2000)); } catch (InterruptedException e) { // 这里建议更优雅的中断处理,而不是直接抛RuntimeException Thread.currentThread().interrupt(); System.out.println(threadName + " 被中断,退出执行"); break; } } } }
额外优化:修复Buffer的take()方法
顺便提一句,你当前的Buffertake()方法没有判断缓冲区是否为空,当缓冲区空的时候调用take()会直接抛出IndexOutOfBoundsException,建议加上空判断逻辑:
public int take() throws InterruptedException { lock.lockInterruptibly(); try { // 新增:缓冲区为空时等待notEmpty信号 while (integers.isEmpty()) { notEmpty.await(); } int item = integers.remove(0); notFull.signal(); return item; } finally { lock.unlock(); } }
总结
- Condition的
await()/signal()方法必须在持有对应Lock锁的代码块中调用,这是Java并发包的硬性规则,违反就会触发IllegalMonitorStateException。 - 缓冲区的并发控制逻辑(锁、Condition等待/通知)应该封装在Buffer内部,对外只提供
put()/take()这类简洁的方法,避免外部代码直接操作锁和Condition,减少出错概率。
内容的提问来源于stack exchange,提问作者Reza




