Java中的Condition接口是java.util.concurrent.locks包下的核心组件,与Lock配合使用,提供比传统Object.wait()/notify()更灵活、精细的线程协调机制。

以下从多维度详解其原理、用法及最佳实践:

1. 核心概念与优势

  • 多条件队列:一个Lock可创建多个Condition对象(如notEmptynotFull),每个对应独立等待队列,实现多线程按条件精准唤醒,避免“惊群效应”(如生产者-消费者模型中,缓冲区满时生产者等待,空时消费者等待)。
  • 灵活控制:支持await()(无限等待)、await(long time, TimeUnit unit)(超时等待)、signal()(唤醒单个线程)、signalAll()(唤醒全部线程)等方法,且await()可响应中断。
  • 与AQS深度集成:基于AbstractQueuedSynchronizer(AQS)实现,等待线程释放锁后加入Condition队列,被唤醒后重新竞争锁。

2. 核心方法与行为

  • await()
    • 线程必须持有锁,否则抛出IllegalMonitorStateException
    • 释放锁并加入Condition等待队列,进入WAITING状态,直到被signal()或中断唤醒。
    • 唤醒后需重新获取锁,从await()返回时保证持有锁。
  • signal()/signalAll()
    • 唤醒Condition队列头部线程(或全部线程),将其移至AQS同步队列参与锁竞争。
    • 需在持有锁时调用,否则抛出异常。
  • 其他方法
    • awaitUninterruptibly():不可中断等待。
    • awaitNanos()/awaitUntil():指定纳秒级超时或截止时间。

3. 内部实现原理

  • 等待队列结构Condition内部维护单向链表队列(firstWaiter/lastWaiter),存储调用await()的线程节点(Node)。
  • await()流程
    1. 构造Node加入Condition队列尾部。
    2. 释放锁(通过fullyRelease方法),唤醒AQS队列中后续线程。
    3. 线程阻塞(LockSupport.park),直到被signal()移至AQS队列。
    4. 唤醒后重新竞争锁,成功则从await()返回。
  • signal()流程
    1. 获取Condition队列头节点,移除并链接至AQS队列尾部。
    2. 唤醒线程参与锁竞争,若竞争成功则继续执行。

4. 典型应用场景

  • 生产者-消费者模型
    class BoundedBuffer<T> {
        private Queue<T> buffer = new LinkedList<>();
        private int capacity;
        private Lock lock = new ReentrantLock();
        private Condition notFull = lock.newCondition();
        private Condition notEmpty = lock.newCondition();
    
        public void put(T item) throws InterruptedException {
            lock.lock();
            try {
                while (buffer.size() == capacity) notFull.await(); // 缓冲区满,生产者等待
                buffer.offer(item);
                notEmpty.signalAll(); // 唤醒消费者
            } finally { lock.unlock(); }
        }
    
        public T take() throws InterruptedException {
            lock.lock();
            try {
                while (buffer.isEmpty()) notEmpty.await(); // 缓冲区空,消费者等待
                T item = buffer.poll();
                notFull.signalAll(); // 唤醒生产者
                return item;
            } finally { lock.unlock(); }
        }
    }
    
  • 多条件协调:如餐厅订单系统,厨师、服务员、顾客分别等待“食材就绪”“菜品就绪”“订单完成”等不同条件。

5. 最佳实践与注意事项

  • 锁与条件绑定:通过Lock.newCondition()创建Condition,确保操作前已获取锁。
  • 循环检查条件:用while而非if检查条件(如while (!conditionMet) await()),防止虚假唤醒。
  • 避免死锁:确保await()signal()调用顺序合理,避免线程相互等待。
  • 中断处理await()可响应中断,需捕获InterruptedException并恢复中断状态。
  • 性能优化signalAll()在需要唤醒所有线程时使用,否则优先signal()减少竞争。

6. 与Object.wait()/notify()对比

特性 Condition Object.wait()/notify()
锁关联 需显式与Lock绑定 隐式与对象监视器绑定
条件队列数量 支持多个独立队列 仅一个全局队列
唤醒精度 可指定唤醒单个或全部线程 notify()随机唤醒,notifyAll()唤醒全部
中断响应 await()可中断 wait()可中断
虚假唤醒处理 需循环检查条件 需循环检查条件

总结Condition通过多条件队列、灵活唤醒策略及与AQS的深度集成,为复杂线程协作场景提供高效解决方案。掌握其用法需理解锁机制、条件检查循环及内部队列管理,结合生产者-消费者等经典模式实践,可显著提升并发程序的可控性与性能。

Logo

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

更多推荐