AQS Condition源码深度解析

本文深入剖析了AQS的Condition队列结构,详细阐述了await()方法的阻塞过程,包括API层的操作、ConditionQueue的进出、释放资源的条件以及从ConditionQueue到SyncQueue的流转。同时,文章讨论了唤醒(signal)过程,并解释了为何await()会保存并根据getState()状态释放资源,以及线程在被中断后无法继续执行的原因。最后,文章总结了AQS Condition的关键点,为理解并发控制提供了深入理解。
摘要由CSDN通过智能技术生成
简介

在上一篇《AQS》ConditionQueue 介绍中提到了 ConditionQueue 是借助 AQS 的 SyncQueue 结构来实现条件变量及等待的功能。

ConditionQueue 是 AQS 中相对特殊、复杂的队列。相比较 SyncQueue 只把资源争抢、线程通信全部在 AQS 中处理,那ConditionQueue 就是为了对标 Object#wait、notify、notifyAll 而设计的数据结构,就是为了让开发人员可以手操线程同行。

队列结构

ConditionQueue 是基于 SyncQueue的,或者说都是基于 Node 结构的。而它的操作入口,相信接触过 JUC 的同学应该都知道,那就是 Condition接口。

AQS 中的相关方法都在 ConditionObject,该类正是实现了 Conditon接口。

public interface Condition {
   
    // 当前线程进入等待状态直到被唤醒或者中断
    void await() throws InterruptedException;
    // 当前线程进入等待状态,不响应中断,阻塞直到被唤醒
    void awaitUninterruptibly();
    // 当前线程进入等待状态直到被唤醒或者中断,阻塞带时间限制
    long awaitNanos(long nanosTimeout) throws InterruptedException;
    // 当前线程进入等待状态直到被唤醒或者中断,阻塞带时间限制
    boolean await(long time, TimeUnit unit) throws InterruptedException;
    // 当前线程进入等待状态直到被唤醒或者中断,阻塞带时间限制
    boolean awaitUntil(Date deadline) throws InterruptedException;
    // 唤醒单个阻塞线程
    void signal();
    // 唤醒所有阻塞线程
    void signalAll();
}    

Condition 可以说目前只有唯一的实现类,那就是 AQS 的 ConditionObject,JUC 中的涉及到 Condtion都是直接使用该类。

上一篇提到 AQS-Node 的核心字段中,一直有个字段nextWaiter没有"真正"被使用到,现在终于被排上用场了。

// 在 condition 上等待的下个结点(独占模式),或者为 SHARED,标识为共享模式,不会被使用
// 为null,就是EXCLUSIVE
Node nextWaiter;

因为在一般使用中,我们仅仅是将其作为标记使用,但是当我们创建了 Condition 类(比如 ReentrantLock.newCondition)之后,就是用来组成 Condition 上的等待队列。

AQS-ConditionQueue

注意

方法总览

上文提到,ConditionQueue 是重用 SyncQueue 的结构,并且 ConditionObject 作为 AQS 的父类,所以在 ConditionObject 的源码中,实际有不少使用 AQS 的方法,尤其是 SyncQueue 和 Node 相关。

ConditionObject 自己实现方法则一般是这三类:

  • 为了提供给 Condition 接口的重写方法,以便复用;
  • 队列相关操作方法,涉及到 ConditionQueue 独有的结构,比如 firstWaiter、lastWaiter;
  • 基于 Condition Queue的线程统计与操作方法。

以此,我们画出 ConditionObject 的方法总览图。

AQS-CondtionObject API

总的来看,核心方法还是比较简单的。注意第2、3层会有使用到 AQS 的方法,主要是涉及到从 ConditionQueue 流转到 SyncQueue,后面会总结什么情况下会出现这样的流转。

阻塞(await)

每个调用 await 阻塞的线程,必然是已经持有锁或者说资源的。我们可以类比 Object.wait() 只能在 synchronized 方法或代码块中调用一样。

因此,基于这个前提,await方法大致主流程如下:

  • 1、如果当前线程被中断,则抛出 InterruptedException。
  • 2、当前线程包装为结点,加入 ConditionQueue
  • 3、保存 getState 返回的锁定状态。
  • 3.1、以保存状态作为参数调用 release ,如果失败则抛出 IllegalMonitorStateException。
  • 4、阻塞,直到发出信号或被中断为止。
  • 5、通过调用以保存状态作为参数的 acquire 专用版本来进行重新 acquire 。
  • 6、临走前做一次“无效”结点的清理
  • 7、如果在步骤4中被阻止而被中断,则抛出 InterruptedException

可能在不同方法之间,有些许差别。比如响应中断,对于 awaitUninterruptibly 是没有的。

1.API层

对于阻塞,其实无非就是对资源、ConditionQueue 出入、SyncQueue 出入的一个编排,代表了对资源的争抢和放弃的流转。

实际流程都是大同小异的,下面就以 await() 举例。

注意,awaitUninterruptibly() 是特殊的,因为不响应中断,所以必然不会因为中断而进入 Sync Queue。后面会看下源码。

然后看看代码,哪些是操作 ConditionQueue、哪些是SyncQueue,剩下的就是线程操作了。

public final void await() throws InterruptedException {
   
    // 1.首先检查中断
    if (Thread.interrupted())
        throw new InterruptedException();
    // 2.添加到条件队列
    Node node = addConditionWaiter();
    // 3.释放资源, 返回原有锁状态
    int savedState = fullyRelease(node);
    int interruptMode = 0;
    while (!isOnSyncQueue(node)) {
   
        // 4.如果不在 等待队列 上, 就继续阻塞
        // 因为被唤醒时, 结点会被转移到 等待队列 上
        LockSupport.park(this);
        // 4-1.校验中断:如果是中断了, 会被转到 等待队列 上
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    // 5.退出阻塞(中断或者被唤醒), 说明已经在队列, 重新循环获取资源了
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    // (因为还在 Lock 代码中, 所以必须拿到锁才能往后执行吧)
    // 6.临走前做一次清理(包括自己),  否则就是等着被人处理
    if (node.ne
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值