《Condition 接口与等待唤醒机制:对标 Object.wait/notify 的精准控制》


一、前言:为什么需要 Condition

synchronized 中,线程通信依赖于 wait() / notify(),但存在明显局限:

  • 必须依附在同一个对象监视器上;
  • 不能精确唤醒指定线程;
  • 不支持多条件队列管理。

为了解决这些问题,JDK5 引入了 Condition 接口,配合 ReentrantLock 使用,
提供更灵活、更精确的等待/唤醒机制。

一句话:ConditionObject.wait/notify 的高级版,底层依托 LockSupport.park/unpark 实现。


二、Condition 的创建与基本使用

Condition 必须依附于一个显式锁(通常是 ReentrantLock):

ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();
常用方法
方法 说明
await() 当前线程等待(释放锁,进入等待队列)
signal() 唤醒一个等待线程
signalAll() 唤醒所有等待线程
awaitNanos(long nanos) 等待指定纳秒时间
awaitUntil(Date deadline) 等待直到某个时间点
awaitUninterruptibly() 不可中断等待

三、工作原理与底层机制

(1)内部结构

每个 Condition 都维护一个独立的 等待队列(Condition Queue)
与 AQS 的同步队列(Sync Queue)分离。

流程概览:

ReentrantLock
 ├── AQS 同步队列(锁竞争队列)
 └── Condition 等待队列(每个 Condition 独立)
(2)await() 的底层逻辑

源码简化:

public final void await() throws InterruptedException {
    if (Thread.interrupted()) throw new InterruptedException();
    Node node = addConditionWaiter();
    int savedState = fullyRelease(node);
    while (!isOnSyncQueue(node)) {
        LockSupport.park(this); // 阻塞当前线程
    }
    acquireQueued(node, savedState);
}

执行流程:

  1. 当前线程持有锁,调用 await()
  2. 线程被加入 Condition 队列;
  3. 释放锁(让出执行权);
  4. 使用 LockSupport.park() 挂起线程;
  5. 被唤醒后重新加入 AQS 队列竞争锁;
  6. 成功获得锁后从 await() 返回。
(3)signal() 的底层逻辑
public final void signal() {
    Node first = firstWaiter;
    if (first != null)
        doSignal(first);
}

执行流程:

  1. 将 Condition 队列中的第一个等待节点转移到 AQS 同步队列;
  2. 调用 LockSupport.unpark() 唤醒对应线程;
  3. 被唤醒的线程重新竞争锁;
  4. 获得锁后从 await() 返回。
(4)Condition 与 LockSupport 的关系
  • Condition 通过 AQS 管理等待队列;
  • 线程挂起与唤醒由 LockSupport.park/unpark 实现;
  • 精确唤醒机制比 notifyAll() 更高效。

四、Condition vs Object.wait/notify

对比项 Condition Object.wait/notify
所属锁类型 ReentrantLock synchronized
等待队列 独立多队列 共享单队列
唤醒粒度 精确(指定条件) 模糊(随机线程)
可中断等待 ✅ 支持 ✅ 支持
多条件支持 ✅ 支持多个 Condition ❌ 仅一个监视器
底层实现 LockSupport.park/unpark JVM Monitor 机制

Condition 提供“多等待队列”的能力,可针对不同条件变量分别控制等待与唤醒逻辑。

示例:

ReentrantLock lock = new ReentrantLock();
Condition notFull = lock.newCondition();
Condition notEmpty = lock.newCondition();

可分别控制“队列满/空”的不同等待队列。


五、典型应用场景与代码示例

(1)生产者-消费者模型(多 Condition 版本)
class BoundedBuffer {
    final Lock lock = new ReentrantLock();
    final Condition notFull = lock.newCondition();
    final Condition notEmpty = lock.newCondition();
    final Object[] items = new Object[5];
    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();
        }
    }
}

说明:

  • notFull:队列满时阻塞生产者;
  • notEmpty:队列空时阻塞消费者;
  • 精准控制唤醒目标,无需 notifyAll() 带来的无效唤醒。
(2)线程池任务队列(内部机制)

ThreadPoolExecutor 中,BlockingQueue.take() 也是基于 Condition 实现的等待/唤醒。


六、面试高频问题与回答模板

问题 答案要点
Q1:Condition 是什么? Condition 是配合 ReentrantLock 使用的线程等待/通知机制。
Q2:Condition 如何实现等待? await() 会释放锁、加入 Condition 队列并通过 LockSupport.park() 挂起线程。
Q3:signal() 如何唤醒线程? 将 Condition 队列中的节点转移到 AQS 队列,并调用 LockSupport.unpark() 唤醒。
Q4:Condition 为什么比 wait/notify 更强? 可支持多个独立条件队列,实现精准唤醒,避免无谓唤醒。
Q5:await() 和 signal() 必须在锁内执行吗? 必须,否则会抛 IllegalMonitorStateException。
Q6:Condition 支持超时等待吗? 支持,await(long, TimeUnit) 可实现定时等待。
Q7:Condition 和 AQS 的关系? Condition 是 AQS 的扩展机制,依托 AQS 的同步与等待队列。

结语

Condition 是 Java 并发通信的精细化工具,
它将传统 wait/notify 机制演进为多队列、可控、精确唤醒的模式。

掌握 Condition,你就理解了 AQS 内核中“线程通信”的真正原理,
也是 ReentrantLockBlockingQueueThreadPoolExecutor 等组件的基础。

下一篇,我将写——
《Semaphore 信号量机制:限流与并发控制的经典实现》
正式进入“并发限流与资源控制”部分。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐