写点什么

详解 AQS 中的 condition 源码原理

  • 2022-11-03
    中国香港
  • 本文字数:2102 字

    阅读完需:约 7 分钟

详解AQS中的condition源码原理

本文分享自华为云社区《AQS中的condition源码原理详细分析》,作者:breakDawn。

condition 的用法


condition 用于显式的等待通知,等待过程可以挂起并释放锁,唤醒后重新拿到锁。


和直接用 lock\unlock 去做等待通知的区别在于,lock 是不会释放锁的,但是利用的 condition 的 await 则可以,且唤醒后会自动重新拿回锁。


Lock lock = new ReentrantLock();Condition condition = lock.newCondition();public void conditionWait() throws InterruptedException {    lock.lock();    try {        	// if(xxxx)判断不满足条件,等待,释放锁            condition.await();    } finally {            lock.unlock();    }}public void conditionSignal() throws InterruptedException {    lock.lock();    try {        	// 做完事情了,通知condition上等待的开始抢占            condition.signal();    } finally {            lock.unlock();    }}
复制代码


也提供了一些支持中断、支持超时的等待方法

condition 和 object.wait/notify 的区别


  1. object 的 wait 依赖 sync, 只能最多有一个等待队列。 而通过 newCondition 可以制造多个等待队列

  2. wait 不支持中断,而 condition 支持

  3. condition 支持等待特定时间

condition 原理分析

超大原理流程图


  • await(), 简单来讲就是把当前线程放入 condition 的等待队列中,然后调用 LockSupport.park 拉起线程。如果被其他线程通过 signal 唤醒,则放入同步队列中竞争锁,竞争成功则返回,否则继续竞争。

  • signal 方法,就是拿到 condition 的等待队列头节点,用 cas 修改节点状态,改成功则唤醒线程。但有可能被别人抢先,所以需要 cas 操作。


代码结构部分:


​ Lock 提供了 newCondition 接口给外部锁调用


​ 而 newCondition()返回的 Condition 是一个接口



​ 这个接口的实现类是 ConditionObject,放在 AQS 抽象类的内部类中


原理实现部分

等待队列


  • 每个 condition 都有一个属于自己的等待队列

  • 每次调用 condition.await, 就插入到等待队列尾部

  • 等待队列插入封装线程的节点时不需要在尾部 CAS, 因为必须先获取锁,才能调用 await,因此不用 CAS 竞争

  • 每个 Lock 只有一个同步队列(用于 lock()时阻塞和竞争用), 但是可能会有多个等待队列(用于 condition 的 await)

等待过程


  1. 添加线程到 condition 的等待队列尾部

  2. 释放占用的锁,并唤醒同步队列的后继节点

  3. 此时肯定不在 aqs 的同步队列中了, 用 park 方法进入阻塞状态

  4. 被唤醒,唤醒时可能是通过 sign()被人放入了同步队列, 也可能是被中断唤醒,因此要做 checkInterruptWhileWaiting 检查看是否继续, 如果同意继续,就继续睡眠,直到进入同步队列

  5. 尝试 acquireQueued 竞争和抢占 state 同步状态

  6. 退出前,顺带用 unlinkCancelledWaiters 清理已经不是 CONDITION 状态的等待队列节点


public final void await() throws InterruptedException {    if (Thread.interrupted())        throw new InterruptedException();    // 添加本线程到等待队列尾部    Node node = addConditionWaiter();    // 释放锁,唤醒同步队列中的后继节点    int savedState = fullyRelease(node);    int interruptMode = 0;    // 如果已经在同步队列中了,说明被成功sign唤醒    while (!isOnSyncQueue(node)) {        // 阻塞挂起        LockSupport.park(this);        // 确认是否需要中断时就退出        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)            break;    }    // 在同步队列中,那就按同步队列的规则在队列中用CAS竞争同步状态    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)        interruptMode = REINTERRUPT;    // 清理已经不是CONDITION状态的等待队列节点    if (node.nextWaiter != null)         unlinkCancelledWaiters();    if (interruptMode != 0)        reportInterruptAfterWait(interruptMode);}
复制代码

唤醒过程 signal()


检查调用 signal 时,是否当前线程获取了锁,不是则抛异常


if (!isHeldExclusively())    throw new IllegalMonitorStateException();
复制代码


获取 condition 队列中的第一个等待节点


Node first = firstWaiter;if (first != null)    doSignal(first);
复制代码


用 CAS 清除 CONDITION 状态


if (!node.compareAndSetWaitStatus(Node.CONDITION, 0))    return false;
复制代码


调用 AQS 的 enq(firstWaitNode),将这个节点放入到同步队列的队尾(需要 CAS 支撑?因为可能是共享的,即使获取了锁也需要竞争)


Node p = enq(node);
复制代码


移动入同步队列成功后(可能经历了几次 CAS),再用 unpark 方法唤醒,那个线程就进入了上面代码中 Park 之后的部分了


int ws = p.waitStatus;if (ws > 0 || !p.compareAndSetWaitStatus(ws, Node.SIGNAL))    LockSupport.unpark(node.thread);
复制代码


如果是 signalAll 方法,则等待队列中每个节点都执行一次 signal 方法,全部移入同步队列中并唤醒(唤醒后他们很可能还会因为抢不到资源而阻塞,但队列位置不同了,也无法再通过 sign 唤醒了)


do {    Node next = first.nextWaiter;    first.nextWaiter = null;    transferForSignal(first);    first = next;} while (first != null);
复制代码


点击关注,第一时间了解华为云新鲜技术~

发布于: 刚刚阅读数: 4
用户头像

提供全面深入的云计算技术干货 2020-07-14 加入

华为云开发者社区,提供全面深入的云计算前景分析、丰富的技术干货、程序样例,分享华为云前沿资讯动态,方便开发者快速成长与发展,欢迎提问、互动,多方位了解云计算! 传送门:https://bbs.huaweicloud.com/

评论

发布
暂无评论
详解AQS中的condition源码原理_开发_华为云开发者联盟_InfoQ写作社区