条件变量(Condition)详解

条件变量(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 的使用场景

  1. 生产者-消费者模型 当缓冲区满时,生产者挂起;当缓冲区空时,消费者挂起,通过 Condition 实现精准唤醒。
  2. 线程间任务协调 例如,主线程等待多个子线程完成任务后再继续执行。
  3. 复杂同步逻辑 使用多个 Condition 分离不同的等待条件,避免过早唤醒(如"非满"和"非空"条件)。

四、Condition 的实现原理

  1. 底层依赖

    • 基于 AbstractQueuedSynchronizer(AQS),每个 Condition 对象内部维护一个 条件队列
  2. 等待队列与同步队列

    • 条件队列 :调用 await() 的线程会进入此队列。
    • 同步队列 :调用 signal() 后,线程从条件队列迁移到锁的同步队列,等待获取锁。
  3. 操作流程

    • 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 的注意事项

  1. 必须持有锁

    • 调用 await()signal() 前必须获取关联的锁,否则抛出 IllegalMonitorStateException
  2. 循环检查条件

    • 使用 while 而非 if 检查条件,防止虚假唤醒(Spurious Wakeup)。
  3. 中断处理

    • await() 可能抛出 InterruptedException,需在代码中处理中断逻辑。
  4. signal() vs signalAll()

    • signal() :更高效,但可能因线程竞争导致某些线程饥饿。
    • signalAll() :唤醒所有线程,确保逻辑正确性,但可能增加竞争。

七、Condition 与 Object 监视器方法的对比

特性 Condition Object 监视器方法(wait/notify)
关联锁 必须与显式锁(如 ReentrantLock)绑定 synchronized 关键字绑定
多条件支持 一个锁可关联多个 Condition 对象 一个对象只能有一个等待队列
灵活性 支持超时、不可中断等待 仅支持基本等待/通知
性能 更细粒度控制,减少不必要的唤醒 唤醒全部线程,可能引发竞争

八、常见问题与解决方案

  1. 死锁风险

    • 确保 signal() 在修改条件后调用,且所有分支都能释放锁(使用 try-finally 块)。
  2. 资源泄漏

    • 确保所有等待线程最终被唤醒(如使用 signalAll() 在关闭时通知所有线程)。
  3. 过早唤醒

    • 通过多个 Condition 分离不同条件(如"非满"和"非空")。

总结

  • 核心价值Condition 提供了比 wait/notify 更灵活的线程协调机制,支持多条件等待和精细控制。
  • 适用场景:生产者-消费者、任务协调、复杂同步逻辑。
  • 最佳实践 :始终在循环中检查条件,优先使用多个 Condition 分离关注点,正确处理中断和锁释放。

通过合理使用 Condition,可以编写出高效且易于维护的多线程程序。

相关推荐
在京奋斗者2 小时前
spring boot自动装配原理
java·spring boot·spring
明天不下雨(牛客同名)4 小时前
为什么 ThreadLocalMap 的 key 是弱引用 value是强引用
java·jvm·算法
多多*5 小时前
Java设计模式 简单工厂模式 工厂方法模式 抽象工厂模式 模版工厂模式 模式对比
java·linux·运维·服务器·stm32·单片机·嵌入式硬件
胡图蛋.6 小时前
Spring Boot 支持哪些日志框架?推荐和默认的日志框架是哪个?
java·spring boot·后端
牛马baby7 小时前
Java高频面试之并发编程-01
java·开发语言·面试
小小大侠客7 小时前
将eclipse中的web项目导入idea
java·eclipse·intellij-idea
不再幻想,脚踏实地7 小时前
MySQL(一)
java·数据库·mysql
吃海鲜的骆驼7 小时前
SpringBoot详细教程(持续更新中...)
java·spring boot·后端
迷雾骑士7 小时前
SpringBoot中WebMvcConfigurer注册多个拦截器(addInterceptors)时的顺序问题(二)
java·spring boot·后端·interceptor
别来无恙✲8 小时前
Mybatis源码分析
java·源码分析