JUC并发编程 条件变量(Condition)实现机制

100 阅读7分钟

条件变量(Condition)实现机制

1. 简单例子

条件变量(Condition)是Java并发编程中的重要同步工具,它提供了比传统的wait/notify机制更加灵活和强大的线程间协调能力。下面通过一个生产者-消费者的例子来展示Condition的基本用法:

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import java.util.LinkedList;
import java.util.Queue;

/**
 * 使用Condition实现的生产者-消费者模式
 */
public class ProducerConsumerExample {
    private final ReentrantLock lock = new ReentrantLock();  // 可重入锁
    private final Condition notFull = lock.newCondition();   // 缓冲区未满条件
    private final Condition notEmpty = lock.newCondition();  // 缓冲区非空条件
    
    private final Queue<String> buffer = new LinkedList<>(); // 缓冲区
    private final int capacity = 10;                         // 缓冲区容量
    
    /**
     * 生产者方法:向缓冲区添加元素
     */
    public void produce(String item) throws InterruptedException {
        lock.lock();  // 获取锁
        try {
            // 当缓冲区满时,生产者等待
            while (buffer.size() == capacity) {
                notFull.await();  // 在notFull条件上等待
            }
            
            buffer.offer(item);  // 添加元素到缓冲区
            System.out.println("生产: " + item + ", 当前缓冲区大小: " + buffer.size());
            
            notEmpty.signal();  // 通知消费者缓冲区非空
        } finally {
            lock.unlock();  // 释放锁
        }
    }
    
    /**
     * 消费者方法:从缓冲区取出元素
     */
    public String consume() throws InterruptedException {
        lock.lock();  // 获取锁
        try {
            // 当缓冲区空时,消费者等待
            while (buffer.isEmpty()) {
                notEmpty.await();  // 在notEmpty条件上等待
            }
            
            String item = buffer.poll();  // 从缓冲区取出元素
            System.out.println("消费: " + item + ", 当前缓冲区大小: " + buffer.size());
            
            notFull.signal();  // 通知生产者缓冲区未满
            return item;
        } finally {
            lock.unlock();  // 释放锁
        }
    }
}

这个例子展示了Condition的核心特性:

  • 多个条件变量:一个锁可以关联多个条件变量(notFull和notEmpty)
  • 精确唤醒:可以选择性地唤醒等待特定条件的线程
  • 避免虚假唤醒:使用while循环检查条件,防止虚假唤醒

2. 调用链分析

2.1 Condition创建调用链

sequenceDiagram
    participant Client as 客户端代码
    participant ReentrantLock as ReentrantLock
    participant Sync as Sync(内部类)
    participant AQS as AbstractQueuedSynchronizer
    participant ConditionObject as ConditionObject
    
    Client->>ReentrantLock: newCondition()
    ReentrantLock->>Sync: newCondition()
    Sync->>AQS: new ConditionObject()
    AQS->>ConditionObject: 创建实例
    ConditionObject-->>Client: 返回Condition实例

2.2 await()方法调用链

sequenceDiagram
    participant Thread as 当前线程
    participant ConditionObject as ConditionObject
    participant AQS as AbstractQueuedSynchronizer
    participant LockSupport as LockSupport
    
    Thread->>ConditionObject: await()
    ConditionObject->>ConditionObject: 检查中断状态
    ConditionObject->>ConditionObject: 创建ConditionNode
    ConditionObject->>ConditionObject: enableWait(node)
    ConditionObject->>AQS: release(savedState)
    ConditionObject->>LockSupport: park()
    Note over Thread: 线程阻塞等待
    ConditionObject->>ConditionObject: canReacquire(node)
    ConditionObject->>AQS: acquire(node, savedState)
    ConditionObject-->>Thread: 返回

2.3 signal()方法调用链

sequenceDiagram
    participant Thread as 信号线程
    participant ConditionObject as ConditionObject
    participant AQS as AbstractQueuedSynchronizer
    participant LockSupport as LockSupport
    participant WaitingThread as 等待线程
    
    Thread->>ConditionObject: signal()
    ConditionObject->>ConditionObject: 检查锁持有状态
    ConditionObject->>ConditionObject: doSignal(firstWaiter, false)
    ConditionObject->>AQS: enqueue(node)
    AQS->>LockSupport: unpark(waitingThread)
    LockSupport-->>WaitingThread: 唤醒线程

3. 核心源码分析

3.1 ConditionObject类结构

/**
 * AbstractQueuedSynchronizer的内部类,实现了Condition接口
 * 为Lock实现提供条件变量支持
 */
public class ConditionObject implements Condition, java.io.Serializable {
    private static final long serialVersionUID = 1173984872572414699L;
    
    /** 条件队列的第一个等待节点 */
    private transient ConditionNode firstWaiter;
    /** 条件队列的最后一个等待节点 */
    private transient ConditionNode lastWaiter;

    /**
     * 创建新的ConditionObject实例
     */
    public ConditionObject() { }
    
    // ... 其他方法实现
}

3.2 ConditionNode节点结构

/**
 * 条件队列中的节点,继承自Node类
 * 实现了ForkJoinPool.ManagedBlocker接口以支持ForkJoinPool
 */
static final class ConditionNode extends Node implements ForkJoinPool.ManagedBlocker {
    /** 指向条件队列中下一个等待节点的链接 */
    ConditionNode nextWaiter;

    /**
     * 允许Condition在ForkJoinPool中使用而不会导致固定池耗尽
     * 仅适用于无超时的Condition等待,不适用于有超时的版本
     */
    public final boolean isReleasable() {
        // 当状态<=1或当前线程被中断时,认为可以释放
        return status <= 1 || Thread.currentThread().isInterrupted();
    }

    /**
     * 阻塞当前线程直到可以释放
     */
    public final boolean block() {
        while (!isReleasable()) {
            LockSupport.park();  // 使用LockSupport阻塞线程
        }
        return true;
    }
}

3.3 await()方法核心实现

/**
 * 实现可中断的条件等待
 * 执行步骤:
 * 1. 如果当前线程被中断,抛出InterruptedException
 * 2. 保存getState()返回的锁状态
 * 3. 使用保存的状态作为参数调用release(),如果失败则抛出IllegalMonitorStateException
 * 4. 阻塞直到被信号唤醒或中断
 * 5. 通过调用acquire()的特殊版本重新获取锁,使用保存的状态作为参数
 * 6. 如果在步骤4中被中断,抛出InterruptedException
 */
public final void await() throws InterruptedException {
    // 1. 检查线程中断状态
    if (Thread.interrupted())
        throw new InterruptedException();
    
    // 2. 创建条件节点
    ConditionNode node = new ConditionNode();
    
    // 3. 启用等待:将节点加入条件队列并释放锁
    int savedState = enableWait(node);
    
    // 4. 设置阻塞器以支持向后兼容
    LockSupport.setCurrentBlocker(this);
    
    boolean interrupted = false, cancelled = false, rejected = false;
    
    // 5. 循环等待直到可以重新获取锁
    while (!canReacquire(node)) {
        // 检查中断状态
        if (interrupted |= Thread.interrupted()) {
            // 如果被中断,尝试取消条件等待
            if (cancelled = (node.getAndUnsetStatus(COND) & COND) != 0)
                break;  // 中断发生在信号之前
        } else if ((node.status & COND) != 0) {
            // 节点仍在条件队列中,需要阻塞
            try {
                if (rejected)
                    node.block();  // 直接阻塞
                else
                    ForkJoinPool.managedBlock(node);  // 使用ForkJoinPool管理的阻塞
            } catch (RejectedExecutionException ex) {
                rejected = true;  // ForkJoinPool拒绝,下次直接阻塞
            } catch (InterruptedException ie) {
                interrupted = true;  // 记录中断状态
            }
        } else
            Thread.onSpinWait();  // 在入队过程中被唤醒,自旋等待
    }
    
    // 6. 清理阻塞器
    LockSupport.setCurrentBlocker(null);
    
    // 7. 清除节点状态
    node.clearStatus();
    
    // 8. 重新获取锁
    acquire(node, savedState, false, false, false, 0L);
    
    // 9. 处理中断
    if (interrupted) {
        if (cancelled) {
            // 中断发生在信号之前,清理取消的等待者并抛出异常
            unlinkCancelledWaiters(node);
            throw new InterruptedException();
        }
        // 中断发生在信号之后,重新设置中断状态
        Thread.currentThread().interrupt();
    }
}

3.4 enableWait()方法实现

/**
 * 将节点添加到条件队列并释放锁
 * @param node 要添加的节点
 * @return 保存的状态,用于重新获取锁
 */
private int enableWait(ConditionNode node) {
    // 检查当前线程是否独占持有锁
    if (isHeldExclusively()) {
        // 设置节点的等待线程
        node.waiter = Thread.currentThread();
        
        // 设置节点状态为条件等待状态
        node.setStatusRelaxed(COND | WAITING);
        
        // 将节点添加到条件队列尾部
        ConditionNode last = lastWaiter;
        if (last == null)
            firstWaiter = node;  // 条件队列为空,设置为第一个节点
        else
            last.nextWaiter = node;  // 链接到队列尾部
        lastWaiter = node;  // 更新尾节点
        
        // 保存当前锁状态
        int savedState = getState();
        
        // 释放锁
        if (release(savedState))
            return savedState;
    }
    
    // 锁未持有或状态不一致,标记节点为取消状态
    node.status = CANCELLED;
    throw new IllegalMonitorStateException();
}

3.5 signal()方法实现

/**
 * 将等待时间最长的线程(如果存在)从此条件的等待队列移动到拥有锁的等待队列
 * @throws IllegalMonitorStateException 如果isHeldExclusively()返回false
 */
public final void signal() {
    ConditionNode first = firstWaiter;
    
    // 检查当前线程是否独占持有锁
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    
    // 如果有等待的节点,执行信号操作
    if (first != null)
        doSignal(first, false);  // false表示只唤醒一个线程
}

/**
 * 将所有线程从此条件的等待队列移动到拥有锁的等待队列
 * @throws IllegalMonitorStateException 如果isHeldExclusively()返回false
 */
public final void signalAll() {
    ConditionNode first = firstWaiter;
    
    // 检查当前线程是否独占持有锁
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    
    // 如果有等待的节点,执行信号操作
    if (first != null)
        doSignal(first, true);  // true表示唤醒所有线程
}

3.6 doSignal()方法实现

/**
 * 移除并转移一个或所有等待者到同步队列
 * @param first 第一个等待节点
 * @param all 是否转移所有节点
 */
private void doSignal(ConditionNode first, boolean all) {
    while (first != null) {
        // 获取下一个等待节点
        ConditionNode next = first.nextWaiter;
        
        // 更新条件队列的头节点
        if ((firstWaiter = next) == null)
            lastWaiter = null;  // 队列变空
        
        // 尝试取消节点的条件状态并将其移动到同步队列
        if ((first.getAndUnsetStatus(COND) & COND) != 0) {
            enqueue(first);  // 将节点加入同步队列
            if (!all)
                break;  // 如果只需要唤醒一个,则退出循环
        }
        
        // 处理下一个节点
        first = next;
    }
}

3.7 canReacquire()方法实现

/**
 * 检查最初放置在条件队列上的节点现在是否准备好在同步队列上重新获取锁
 * @param node 要检查的节点
 * @return 如果正在重新获取则返回true
 */
private boolean canReacquire(ConditionNode node) {
    // 检查链接而不是状态,以避免入队竞争
    // 节点不为null,有前驱节点,且已在同步队列中
    return node != null && node.prev != null && isEnqueued(node);
}

4. 条件变量的工作机制

4.1 数据结构设计

classDiagram
    class AbstractQueuedSynchronizer {
        -Node head
        -Node tail
        -int state
        +acquire()
        +release()
    }
    
    class ConditionObject {
        -ConditionNode firstWaiter
        -ConditionNode lastWaiter
        +await()
        +signal()
        +signalAll()
    }
    
    class ConditionNode {
        +ConditionNode nextWaiter
        +Thread waiter
        +int status
    }
    
    class Node {
        +Node prev
        +Node next
        +Thread waiter
        +int status
    }
    
    AbstractQueuedSynchronizer "1" *-- "*" ConditionObject : contains
    ConditionObject "1" *-- "*" ConditionNode : manages
    ConditionNode --|> Node : extends

4.2 状态转换图

stateDiagram-v2
    [*] --> Running : 线程执行
    Running --> ConditionWaiting : await()
    ConditionWaiting --> SyncWaiting : signal()/signalAll()
    SyncWaiting --> Running : 获取到锁
    ConditionWaiting --> Interrupted : 线程中断
    Interrupted --> [*] : 抛出InterruptedException
    SyncWaiting --> Interrupted : 线程中断

4.3 队列结构图

flowchart TD
    subgraph AQS["同步队列 (AQS)"]
        H[Head] --> N1[Node1]
        N1 --> N2[Node2]
        N2 --> T[Tail]
    end
    
    subgraph COND["条件队列 (Condition)"]
        FW[FirstWaiter] --> CN1[ConditionNode1]
        CN1 --> CN2[ConditionNode2]
        CN2 --> LW[LastWaiter]
    end
    
    CN1 -."signal()".-> N2
    CN2 -."signal()".-> T
    
    style H fill:#f9f,stroke:#333,stroke-width:2px
    style FW fill:#9f9,stroke:#333,stroke-width:2px
    style AQS fill:#e1f5fe,stroke:#01579b,stroke-width:2px
    style COND fill:#f3e5f5,stroke:#4a148c,stroke-width:2px

总结

核心特性

  1. 双队列设计

    • 同步队列:管理等待获取锁的线程
    • 条件队列:管理等待特定条件的线程
  2. 精确唤醒机制

    • signal():只唤醒一个等待线程
    • signalAll():唤醒所有等待线程
    • 避免了传统wait/notify的"惊群效应"
  3. 中断处理

    • 支持可中断和不可中断的等待
    • 正确处理中断与信号的时序关系
  4. 超时支持

    • 提供多种超时等待方法
    • 支持绝对时间和相对时间超时

实现优势

  1. 性能优化

    • 使用CAS操作减少锁竞争
    • 支持ForkJoinPool的ManagedBlocker接口
    • 避免不必要的线程唤醒
  2. 灵活性

    • 一个锁可以关联多个条件变量
    • 支持公平和非公平模式
    • 提供丰富的监控和调试接口
  3. 可靠性

    • 正确处理虚假唤醒
    • 保证线程安全
    • 支持序列化

使用建议

  1. 正确的使用模式

    lock.lock();
    try {
        while (!condition) {
            conditionVar.await();
        }
        // 执行业务逻辑
    } finally {
        lock.unlock();
    }
    
  2. 避免常见错误

    • 必须在持有锁的情况下调用await/signal
    • 使用while循环而不是if来检查条件
    • 正确处理InterruptedException
  3. 性能考虑

    • 合理选择signal()和signalAll()
    • 避免长时间持有锁
    • 考虑使用超时版本的await方法

Java的Condition实现展现了现代并发编程框架的设计精髓:通过精心设计的数据结构、高效的算法和完善的错误处理机制,为开发者提供了强大而易用的并发工具。