条件变量(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,可以编写出高效且易于维护的多线程程序。

相关推荐
陈小桔1 小时前
idea中重新加载所有maven项目失败,但maven compile成功
java·maven
小学鸡!1 小时前
Spring Boot实现日志链路追踪
java·spring boot·后端
xiaogg36781 小时前
阿里云k8s1.33部署yaml和dockerfile配置文件
java·linux·kubernetes
逆光的July1 小时前
Hikari连接池
java
微风粼粼1 小时前
eclipse 导入javaweb项目,以及配置教程(傻瓜式教学)
java·ide·eclipse
番茄Salad1 小时前
Spring Boot临时解决循环依赖注入问题
java·spring boot·spring cloud
天若有情6732 小时前
Spring MVC文件上传与下载全面详解:从原理到实战
java·spring·mvc·springmvc·javaee·multipart
祈祷苍天赐我java之术2 小时前
Redis 数据类型与使用场景
java·开发语言·前端·redis·分布式·spring·bootstrap
Olrookie3 小时前
若依前后端分离版学习笔记(二十)——实现滑块验证码(vue3)
java·前端·笔记·后端·学习·vue·ruoyi
倚栏听风雨3 小时前
java.lang.SecurityException异常
java