Condition 接口
比 wait/notify 更灵活的条件等待。
为什么需要 Condition?
Object.wait()/notify() 只有一个条件队列,如果需要多个等待条件怎么办?
java
// wait/notify 的问题:只有一个条件队列
synchronized (lock) {
// 队列满时等
while (queue.isFull()) {
lock.wait();
}
// 但唤醒后,可能队列还是满的(多个生产者都被唤醒了)
}Condition 可以创建多个条件队列:
java
Lock lock = new ReentrantLock();
Condition notFull = lock.newCondition(); // 队列不满
Condition notEmpty = lock.newCondition(); // 队列不空
Condition done = lock.newCondition(); // 完成信号Condition vs Object wait/notify
| 对比 | Object wait/notify | Condition |
|---|---|---|
| 条件队列 | 每个对象一个 | 每个锁可创建多个 |
| 公平性 | 不可控 | 可选公平/非公平 |
| 超时等待 | wait(timeout) | await(timeout, TimeUnit) |
| 中断支持 | 支持 | 支持,且有 awaitUninterruptibly() |
基本用法
java
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
lock.lock();
try {
while (!conditionMet) {
condition.await(); // 等
}
// 执行任务
condition.signal(); // 通知
} finally {
lock.unlock();
}Condition 方法一览
| 方法 | 作用 |
|---|---|
await() | 等待,可被中断 |
awaitUninterruptibly() | 等待,不响应中断 |
awaitNanos(long nanos) | 等待指定纳秒数 |
await(long time, TimeUnit) | 等待指定时间 |
signal() | 唤醒一个等待线程 |
signalAll() | 唤醒所有等待线程 |
经典示例:生产者-消费者
java
public class BoundedBuffer<T> {
private final Lock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition();
private final Condition notEmpty = lock.newCondition();
private final T[] items;
private int count, head, tail;
@SuppressWarnings("unchecked")
public BoundedBuffer(int capacity) {
items = (T[]) new Object[capacity];
}
public void put(T item) throws InterruptedException {
lock.lock();
try {
while (count == items.length) {
notFull.await(); // 队列满,等「不满」
}
items[tail] = item;
if (++tail == items.length) tail = 0;
count++;
notEmpty.signal(); // 通知「不空」
} finally {
lock.unlock();
}
}
public T take() throws InterruptedException {
lock.lock();
try {
while (count == 0) {
notEmpty.await(); // 队列空,等「非空」
}
T item = items[head];
items[head] = null; // 帮助 GC
if (++head == items.length) head = 0;
count--;
notFull.signal(); // 通知「不满」
return item;
} finally {
lock.unlock();
}
}
}关键:生产者等 notFull,消费者等 notEmpty,互不干扰。
带超时的等待
java
lock.lock();
try {
// 等 5 秒,不满足就放弃
boolean success = condition.await(5, TimeUnit.SECONDS);
if (success) {
// 条件满足,执行任务
} else {
// 超时,执行其他逻辑
}
} finally {
lock.unlock();
}awaitNanos:精确超时
java
lock.lock();
try {
long nanosLeft = TimeUnit.SECONDS.toNanos(5);
while (!conditionMet && nanosLeft > 0) {
nanosLeft = condition.awaitNanos(nanosLeft); // 返回剩余时间
}
if (conditionMet) {
// 条件满足
} else {
// 超时
}
} finally {
lock.unlock();
}awaitUninterruptibly:不响应中断
某些场景下,不希望被中断打断:
java
lock.lock();
try {
condition.awaitUninterruptibly(); // 忽略中断,继续等
} finally {
lock.unlock();
}场景:后台任务,必须等待某个条件,除非程序退出。
signal vs signalAll
和 wait/notify 类似:
| signal() | signalAll() | |
|---|---|---|
| 唤醒数量 | 一个 | 所有 |
| 风险 | 可能饥饿 | 更安全 |
java
// 单个生产者/消费者,用 signal 就够
notEmpty.signal(); // 只唤醒一个消费者
// 多个生产者/消费者,用 signalAll 更安全
notEmpty.signalAll(); // 唤醒所有消费者对比 wait/notify 的优势
1. 多个条件队列
java
// wait/notify:所有等待共用一个队列
synchronized (lock) {
if (isFull()) wait();
if (isEmpty()) wait(); // 和 isFull 混在一起
}
// Condition:每个条件独立队列
Condition notFull = lock.newCondition();
Condition notEmpty = lock.newCondition();
if (isFull()) notFull.await(); // 只在这个队列等
if (isEmpty()) notEmpty.await(); // 在另一个队列等2. 精确唤醒
java
// wait/notify:随机唤醒
lock.notify(); // 可能唤醒错的线程
// Condition:可以绑定到特定条件
notEmpty.signal(); // 只唤醒等「非空」的线程3. 更丰富的超时控制
java
// wait:只能等待毫秒
lock.wait(5000);
// Condition:支持各种时间单位
condition.await(5, TimeUnit.SECONDS);
condition.await(5_000_000_000L, TimeUnit.NANOSECONDS);
condition.awaitUntil(deadline);注意事项
1. 必须在锁内使用
java
// ❌ 错误
Condition condition = lock.newCondition();
condition.await(); // 抛异常
// ✅ 正确
lock.lock();
try {
condition.await();
} finally {
lock.unlock();
}2. 永远在 finally 中释放锁
java
lock.lock();
try {
while (!conditionMet) {
condition.await();
}
} finally {
lock.unlock(); // 必须释放,否则死锁
}3. signal 不立即交出锁
java
lock.lock();
try {
condition.signal(); // 通知了,但还没释放锁
// 这段代码继续执行
} finally {
lock.unlock(); // 这里才释放锁
}总结
- Condition 可以创建多个条件队列,比 wait/notify 更灵活
await()系列方法比wait()功能更丰富(支持超时、中断控制)signal()只唤醒一个,可能饥饿;signalAll()唤醒所有,更安全- 必须在锁内使用,永远在 finally 中释放锁
- 适合复杂的多线程协作场景
