详解 AQS 中的 condition 源码原理
本文分享自华为云社区《AQS中的condition源码原理详细分析》,作者:breakDawn。
condition 的用法
condition 用于显式的等待通知,等待过程可以挂起并释放锁,唤醒后重新拿到锁。
和直接用 lock\unlock 去做等待通知的区别在于,lock 是不会释放锁的,但是利用的 condition 的 await 则可以,且唤醒后会自动重新拿回锁。
也提供了一些支持中断、支持超时的等待方法
condition 和 object.wait/notify 的区别
object 的 wait 依赖 sync, 只能最多有一个等待队列。 而通过 newCondition 可以制造多个等待队列
wait 不支持中断,而 condition 支持
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)
等待过程
添加线程到 condition 的等待队列尾部
释放占用的锁,并唤醒同步队列的后继节点
此时肯定不在 aqs 的同步队列中了, 用 park 方法进入阻塞状态
被唤醒,唤醒时可能是通过 sign()被人放入了同步队列, 也可能是被中断唤醒,因此要做 checkInterruptWhileWaiting 检查看是否继续, 如果同意继续,就继续睡眠,直到进入同步队列
尝试 acquireQueued 竞争和抢占 state 同步状态
退出前,顺带用 unlinkCancelledWaiters 清理已经不是 CONDITION 状态的等待队列节点
唤醒过程 signal()
检查调用 signal 时,是否当前线程获取了锁,不是则抛异常
获取 condition 队列中的第一个等待节点
用 CAS 清除 CONDITION 状态
调用 AQS 的 enq(firstWaitNode),将这个节点放入到同步队列的队尾(需要 CAS 支撑?因为可能是共享的,即使获取了锁也需要竞争)
移动入同步队列成功后(可能经历了几次 CAS),再用 unpark 方法唤醒,那个线程就进入了上面代码中 Park 之后的部分了
如果是 signalAll 方法,则等待队列中每个节点都执行一次 signal 方法,全部移入同步队列中并唤醒(唤醒后他们很可能还会因为抢不到资源而阻塞,但队列位置不同了,也无法再通过 sign 唤醒了)
版权声明: 本文为 InfoQ 作者【华为云开发者联盟】的原创文章。
原文链接:【http://xie.infoq.cn/article/165ba3ece9f0ade8d501cd2a1】。文章转载请联系作者。
评论