• 详解AQS中的condition源码原理


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

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

    condition的用法

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

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

    1. Lock lock = new ReentrantLock();
    2. Condition condition = lock.newCondition();
    3. public void conditionWait() throws InterruptedException {
    4. lock.lock();
    5. try {
    6. // if(xxxx)判断不满足条件,等待,释放锁
    7. condition.await();
    8. } finally {
    9. lock.unlock();
    10. }
    11. }
    12. public void conditionSignal() throws InterruptedException {
    13. lock.lock();
    14. try {
    15. // 做完事情了,通知condition上等待的开始抢占
    16. condition.signal();
    17. } finally {
    18. lock.unlock();
    19. }
    20. }

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

    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状态的等待队列节点
    1. public final void await() throws InterruptedException {
    2. if (Thread.interrupted())
    3. throw new InterruptedException();
    4. // 添加本线程到等待队列尾部
    5. Node node = addConditionWaiter();
    6. // 释放锁,唤醒同步队列中的后继节点
    7. int savedState = fullyRelease(node);
    8. int interruptMode = 0;
    9. // 如果已经在同步队列中了,说明被成功sign唤醒
    10. while (!isOnSyncQueue(node)) {
    11. // 阻塞挂起
    12. LockSupport.park(this);
    13. // 确认是否需要中断时就退出
    14. if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
    15. break;
    16. }
    17. // 在同步队列中,那就按同步队列的规则在队列中用CAS竞争同步状态
    18. if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
    19. interruptMode = REINTERRUPT;
    20. // 清理已经不是CONDITION状态的等待队列节点
    21. if (node.nextWaiter != null)
    22. unlinkCancelledWaiters();
    23. if (interruptMode != 0)
    24. reportInterruptAfterWait(interruptMode);
    25. }

    唤醒过程signal()

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

    1. if (!isHeldExclusively())
    2. throw new IllegalMonitorStateException();

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

    1. Node first = firstWaiter;
    2. if (first != null)
    3. doSignal(first);

    3.用CAS清除CONDITION状态

    1. if (!node.compareAndSetWaitStatus(Node.CONDITION, 0))
    2. return false;

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

    Node p = enq(node);

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

    1. int ws = p.waitStatus;
    2. if (ws > 0 || !p.compareAndSetWaitStatus(ws, Node.SIGNAL))
    3. LockSupport.unpark(node.thread);

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

    1. do {
    2. Node next = first.nextWaiter;
    3. first.nextWaiter = null;
    4. transferForSignal(first);
    5. first = next;
    6. } while (first != null);

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

  • 相关阅读:
    链表专项练习(二)
    热门Java开发工具IDEA入门指南——初次运行IntelliJ IDEA
    数据结构 - 栈和队列
    HarmonyOS NEXT应用开发之Axios获取解析网络数据
    【十问十答】回归模型知识点
    Kafka详解(一)
    【软件与系统安全】AFL模糊测试实验
    基于超表面光学,科研人员发明盐粒大小的相机
    Git与Repo:开源开发的得力工具组合
    应用层如何感知传输层链路故障
  • 原文地址:https://blog.csdn.net/devcloud/article/details/127670333