You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

基于ReentrantLock实现的生产者-消费者缓冲区出现java.lang.IllegalMonitorStateException异常的问题排查

排查Java生产者-缓冲区实现中的IllegalMonitorStateException异常

这个异常我太熟了,咱们一步步拆解问题,再给出解决办法:

异常原因分析

首先得明确:java.lang.IllegalMonitorStateException的核心触发条件是调用Condition的await()/signal()方法时,当前线程没有持有该Condition关联的Lock锁

看你写的Publisher代码:

  1. 你从Buffer里直接拿到了notFull这个Condition对象
  2. run()方法的循环里,直接调用notFull.await(),但此时你的Publisher线程根本没有获取Buffer中那个ReentrantLock锁!
  3. 更关键的是,你在Publisher里重复实现了Buffer已经做的逻辑——Buffer的put()方法内部已经通过while(isFull()) notFull.await()处理了缓冲区满的等待,外部完全没必要再重复判断。

这种直接操作外部Condition且不持有对应锁的行为,必然触发IllegalMonitorStateException。

解决方法

第一步:精简Publisher类,移除所有Condition相关代码

把Publisher里的notFullnotEmpty成员变量,以及构造方法中获取它们的逻辑全部删掉,因为这些操作完全没必要,还会引入错误。

第二步:简化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

火山引擎 最新活动