概述

Java 的 Condition 是与 Lock 配合使用的线程协调机制。
它解决的不是“多个线程不能同时执行”,而是:
- 线程已经获得锁,但当前业务条件不满足,需要先释放锁等待;
- 其他线程改变条件后,再通知它回来继续执行。
为什么需要 Condition
假设消费者要从队列取数据:
public T take() {
lock.lock();
try {
return queue.remove();
} finally {
lock.unlock();
}
}
虽然锁保证了线程安全,但队列可能为空。
一种错误做法是持有锁不断轮询:
lock.lock();
try {
while (queue.isEmpty()) {
// 一直循环
}
return queue.remove();
} finally {
lock.unlock();
}
这段代码存在两个严重问题:
- 消费者一直占用 CPU
- 消费者始终持有锁,生产者无法获得锁并添加数据
这不属于典型的循环等待死锁,但会造成系统无法推进:消费者一直持锁空转,生产者永久无法进入临界区。
消费者持有锁
↓
消费者等待数据
↓
生产者需要获得锁才能添加数据
↓
生产者永远无法获得锁
Condition.await() 可以让消费者:
- 暂时释放锁
- 进入等待状态
- 让生产者获得锁
- 等生产者添加数据后通知消费者
- 消费者重新获取锁
- 再次检查队列是否非空
核心方法
接口定义可以简化为:
public interface Condition {
void await() throws InterruptedException;
void awaitUninterruptibly();
long awaitNanos(long nanosTimeout)
throws InterruptedException;
boolean await(long time, TimeUnit unit)
throws InterruptedException;
boolean awaitUntil(Date deadline)
throws InterruptedException;
void signal();
void signalAll();
}
await():释放锁并等待
基本用法:
lock.lock();
try {
while (queue.isEmpty()) {
notEmpty.await();
}
return queue.remove();
} finally {
lock.unlock();
}
假设消费者线程已经获得锁:
flowchart TD
A["消费者获得 Lock"] --> B["检查 queue.isEmpty()"]
B --> C{"队列是否为空?"}
C -- "否:条件满足" --> D["继续消费队列元素"]
C -- "是:条件不满足" --> E["调用 notEmpty.await()"]
E --> F["加入 notEmpty 条件队列"]
F --> G["完全释放 Lock"]
G --> H["消费者线程挂起"]
生产者获得锁并添加数据:
flowchart TD
A["生产者获得 Lock"] --> B["向队列添加数据"]
B --> C["调用 notEmpty.signal()"]
C --> D["消费者从条件队列<br/>转入锁的同步队列"]
D --> E["生产者继续持有锁"]
E --> F["生产者调用 unlock()"]
消费者开始重新竞争锁:
flowchart TD
A["消费者重新竞争 Lock"] --> B["成功获取 Lock"]
B --> C["await() 返回"]
C --> D["重新检查 queue.isEmpty()"]
D --> E["条件满足,取出数据"]
最关键的一点是:await() 返回之前,当前线程必须重新获得对应的锁。因此,await() 前后,线程都在锁保护的临界区内。
await() 是可中断的:
lock.lock();
try {
while (queue.isEmpty()) {
notEmpty.await();
}
return queue.remove();
} finally {
lock.unlock();
}
这里让 InterruptedException 直接向上传播即可;只有在当前层吞掉异常、无法继续抛出时,才通常需要重新设置中断状态。
awaitUninterruptibly():不可中断等待
方法定义:
void awaitUninterruptibly();
使用方式:
lock.lock();
try {
while (!conditionSatisfied()) {
condition.awaitUninterruptibly();
}
doSomething();
} finally {
lock.unlock();
}
如果线程等待期间收到中断,它不会抛出 InterruptedException,而是继续等待条件成立。
这与 Lock.lock() 的不可中断获取语义类似:接口保证它返回时仍保留等待期间收到的中断状态。具体如何记录和恢复属于实现细节,不应依赖“先清除再重新标记”的内部步骤。
condition.awaitUninterruptibly();
if (Thread.currentThread().isInterrupted()) {
// 等待期间可能收到过中断
}
signal():通知一个等待线程
基本使用:
lock.lock();
try {
queue.add(element);
notEmpty.signal();
} finally {
lock.unlock();
}
对于 ReentrantLock 返回的 Condition,signal() 会选择条件队列中的一个等待线程,将其转移到锁的同步等待队列。
signal() 通常会选择条件队列中等待时间较早、尚未取消的线程,但不应把它理解成严格的业务执行顺序。
被选中的线程还需要进入同步队列、重新竞争锁。

等待队列
区分
这里需要区分 Condition 的等待队列和 Java AQS 的同步队列。
等待获取锁的线程位于 AQS 同步队列:
- 其目标是获取
ReentrantLock
AQS 同步队列
[线程 A] → [线程 B] → [线程 C]
调用 await() 的线程位于某个 Condition 的条件队列:
- 其目标是:等待某个业务条件成立
notEmpty 条件队列
[消费者 1] → [消费者 2]
notFull 条件队列
[生产者 1] → [生产者 2]
实现
我们可以多次调用 newCondition() 方法创建多个 Condition 对象,也就是一个 lock 可以持有多个等待队列。

持有多个队列的好处是:提供更细粒度的控制,比如生产者和消费者问题。