Java中用ReentrantLock+两个Condition(notFull、notEmpty)和计数器实现线程安全环形缓冲区:生产者put时满则await notFull,写入后signal notEmpty;消费者take时空则await notEmpty,读取后signal notFull;所有状态操作均在锁内,用while防虚假唤醒。

Java 中用 Condition 实现多生产者多消费者的环形缓冲区(Ring Buffer),核心是借助 ReentrantLock + 多个 Condition 实现精准的阻塞/唤醒控制:一个条件变量管“非满”,一个管“非空”,避免 notifyAll() 的无效唤醒。
环形缓冲区的基本结构设计
定义固定大小数组,维护两个指针:head(消费者读位置)、tail(生产者写位置)。容量为 N 时,实际可用空间为 N-1(留一个空位区分满/空),或用额外计数器。推荐用计数器方式更直观:
-
buffer:Object[] 或泛型数组 -
lock:ReentrantLock 实例 -
notFull:Condition,当缓冲区满时,生产者 await -
notEmpty:Condition,当缓冲区空时,消费者 await -
count:原子整数或受锁保护的 int,记录当前元素个数
生产者线程的 put 操作(线程安全 + 阻塞)
生产者调用 put() 时,先加锁,检查是否已满;满则 notFull.await() 挂起,被唤醒后继续尝试写入。写完后通知消费者(notEmpty.signal()):
public void put(T item) throws InterruptedException {
lock.lock();
try {
while (count == buffer.length) {
notFull.await(); // 等待有空位
}
buffer[tail] = item;
tail = (tail + 1) % buffer.length;
count++;
notEmpty.signal(); // 唤醒至少一个消费者
} finally {
lock.unlock();
}
}
注意用 while 而非 if —— 防止虚假唤醒或多个生产者竞争下再次变满。
立即学习“Java免费学习笔记(深入)”;
消费者线程的 take 操作(线程安全 + 阻塞)
消费者调用 take(),加锁后检查是否为空;空则 notEmpty.await(),唤醒后读取、更新指针、通知生产者:
public T take() throws InterruptedException {
lock.lock();
try {
while (count == 0) {
notEmpty.await(); // 等待有数据
}
T item = (T) buffer[head];
buffer[head] = null; // 避免内存泄漏(对引用类型)
head = (head + 1) % buffer.length;
count--;
notFull.signal(); // 唤醒至少一个生产者
return item;
} finally {
lock.unlock();
}
}
signal() 而非 signalAll() 即可,因只需唤醒一个对应角色线程,减少上下文切换开销。
支持多生产者/多消费者的正确性保障
关键不在 Condition 数量,而在锁的粒度与状态检查逻辑:
- 所有共享状态(
head,tail,count,buffer元素)都必须在lock保护下访问 -
notFull和notEmpty是同一把锁上的不同条件队列,各自管理不同等待组 - 每个
await()必须配对signal()(在修改状态后),且用while循环重检条件 - 不依赖
volatile或 CAS 替代锁 —— 这里需要的是互斥+等待通知,不是无锁优化
这样,任意数量的生产者和消费者线程均可并发安全运行,不会出现覆盖、空读、越界或死锁。


















