条件变量(Condition)详解
一、Condition 的基本概念
条件变量(Condition) 是 Java 并发包(java.util.concurrent.locks
)中用于线程间协调的机制,通常与显式锁(如 ReentrantLock
)配合使用。它的核心功能是允许线程在特定条件未满足时挂起(阻塞),直到其他线程修改条件后唤醒它。
二、Condition 的核心方法
方法 | 描述 |
---|---|
void await() |
释放锁并挂起线程,直到被唤醒或中断。 |
void signal() |
唤醒一个等待线程(随机选择)。 |
void signalAll() |
唤醒所有等待线程。 |
boolean await(long time, TimeUnit unit) |
带超时的等待,超时后自动恢复。 |
void awaitUninterruptibly() |
不可中断的等待(需手动处理中断)。 |
三、Condition 的使用场景
- 生产者-消费者模型 当缓冲区满时,生产者挂起;当缓冲区空时,消费者挂起,通过
Condition
实现精准唤醒。 - 线程间任务协调 例如,主线程等待多个子线程完成任务后再继续执行。
- 复杂同步逻辑 使用多个
Condition
分离不同的等待条件,避免过早唤醒(如"非满"和"非空"条件)。
四、Condition 的实现原理
-
底层依赖
- 基于
AbstractQueuedSynchronizer(AQS)
,每个Condition
对象内部维护一个 条件队列。
- 基于
-
等待队列与同步队列
- 条件队列 :调用
await()
的线程会进入此队列。 - 同步队列 :调用
signal()
后,线程从条件队列迁移到锁的同步队列,等待获取锁。
- 条件队列 :调用
-
操作流程
await()
:释放锁 → 线程加入条件队列 → 挂起。signal()
:将条件队列的头节点移动到锁的同步队列 → 线程尝试获取锁。
五、Condition 的使用示例(生产者-消费者模型)
java
import java.util.concurrent.locks.*;
public class BoundedBuffer {
private final ReentrantLock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition(); // 缓冲区未满条件
private final Condition notEmpty = lock.newCondition(); // 缓冲区非空条件
private final Object[] items = new Object[10];
private int putPtr, takePtr, count;
// 生产者方法
public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length) {
notFull.await(); // 缓冲区已满,等待
}
items[putPtr] = x;
if (++putPtr == items.length) putPtr = 0;
count++;
notEmpty.signal(); // 通知消费者可消费
} finally {
lock.unlock();
}
}
// 消费者方法
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0) {
notEmpty.await(); // 缓冲区为空,等待
}
Object x = items[takePtr];
if (++takePtr == items.length) takePtr = 0;
count--;
notFull.signal(); // 通知生产者可生产
return x;
} finally {
lock.unlock();
}
}
}
六、Condition 的注意事项
-
必须持有锁
- 调用
await()
和signal()
前必须获取关联的锁,否则抛出IllegalMonitorStateException
。
- 调用
-
循环检查条件
- 使用
while
而非if
检查条件,防止虚假唤醒(Spurious Wakeup)。
- 使用
-
中断处理
await()
可能抛出InterruptedException
,需在代码中处理中断逻辑。
-
signal() vs signalAll()
signal()
:更高效,但可能因线程竞争导致某些线程饥饿。signalAll()
:唤醒所有线程,确保逻辑正确性,但可能增加竞争。
七、Condition 与 Object 监视器方法的对比
特性 | Condition | Object 监视器方法(wait/notify) |
---|---|---|
关联锁 | 必须与显式锁(如 ReentrantLock )绑定 |
与 synchronized 关键字绑定 |
多条件支持 | 一个锁可关联多个 Condition 对象 |
一个对象只能有一个等待队列 |
灵活性 | 支持超时、不可中断等待 | 仅支持基本等待/通知 |
性能 | 更细粒度控制,减少不必要的唤醒 | 唤醒全部线程,可能引发竞争 |
八、常见问题与解决方案
-
死锁风险
- 确保
signal()
在修改条件后调用,且所有分支都能释放锁(使用try-finally
块)。
- 确保
-
资源泄漏
- 确保所有等待线程最终被唤醒(如使用
signalAll()
在关闭时通知所有线程)。
- 确保所有等待线程最终被唤醒(如使用
-
过早唤醒
- 通过多个
Condition
分离不同条件(如"非满"和"非空")。
- 通过多个
总结
- 核心价值 :
Condition
提供了比wait/notify
更灵活的线程协调机制,支持多条件等待和精细控制。 - 适用场景:生产者-消费者、任务协调、复杂同步逻辑。
- 最佳实践 :始终在循环中检查条件,优先使用多个
Condition
分离关注点,正确处理中断和锁释放。
通过合理使用 Condition
,可以编写出高效且易于维护的多线程程序。