跳转到主内容
新派编程网 - 新潮编程理念,全新技术课堂!

AQS 的 acquireShared:解析共享模式下节点唤醒后如何递归触发后续节点的级联唤醒

acquireShared的核心是级联唤醒而非单次获取,通过tryAcquireShared返回值(正数触发传播)、setHeadAndPropagate设头并判断是否传播、doReleaseShared自旋CAS唤醒后继共享节点实现接力式唤醒。

acquireShared 的核心不是“抢到就完事”,而是“抢到后主动把门推开,让后面的人也能进”。它通过setHeadAndPropagate和doReleaseShared两个关键环节,实现节点唤醒后的级联传播——不是靠一次释放唤醒一个,而是靠每个成功获取的线程“接力”唤醒下一个。

tryAcquireShared 返回值决定是否启动传播这个方法的返回值不是简单的 true/false,而是带语义的整数:负数:没拿到,入队等待0:拿到了,但资源刚好用尽,不传播唤醒(比如 CountDownLatch 倒计数归零)

正数:拿到了,且还有剩余资源,必须传播——这是级联唤醒的开关例如 Semaphore 许可数为 3,第一个线程调用 tryAcquireShared 返回 2(拿走 1 个,剩 2),这个 >0 的返回值会直接触发 setHeadAndPropagate 中的传播逻辑。

setHeadAndPropagate 是传播的起点当一个共享节点成功获取资源并成为新 head 后,该方法会做三件事:把当前节点设为 head检查 propagate 参数(即 tryAcquireShared 的返回值)是否 >0,或前驱 head 状态异常(如被取消)

若满足条件,立即调用doReleaseShared()尝试唤醒后继共享节点注意:这里不判断“后继有没有资源”,只判断“我拿到后还剩不多?或者队列可能卡住了?”,只要有一点可能性,就往下推信号。

doReleaseShared 实现自旋+CAS 的可靠传播这不是单次唤醒操作,而是一个轻量级自旋循环:先尝试 CAS 修改 head 的 waitStatus 为 PROPAGATE,标记“正在传播”

再检查 head 是否有后继,且后继是共享节点(node.next.isShared())

如果有,调用 unparkSuccessor 唤醒它;唤醒后,那个线程在 setHeadAndPropagate 里又会做同样判断整个过程可能重复多次,直到某次发现:没有后继、后继不是共享节点、或资源已彻底耗尽(tryAcquireShared 返回 ≤0)

这种设计避免了“只叫醒一个却剩一堆许可没人拿”的吞吐瓶颈,也防止了无意义的惊群唤醒。

共享节点入队时就自带传播基因AQS 队列中混排着 EXCLUSIVE(null)和 SHARED(静态实例)两类节点。doAcquireShared 添加节点时明确传入 Node.SHARED,这决定了后续所有唤醒逻辑都会识别它、适配它:shouldParkAfterFailedAcquire 会跳过 SHARED 节点的 waitStatus 检查setHeadAndPropagate 只对 SHARED 节点执行传播分支doReleaseShared 只对 SHARED 后继调用 unparkSuccessor也就是说,一个节点从入队那一刻起,就决定了它被唤醒后要不要“传话”。不是锁机制决定传播,而是节点类型决定了行为契约。

相关文章