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
也就是说,一个节点从入队那一刻起,就决定了它被唤醒后要不要“传话”。不是锁机制决定传播,而是节点类型决定了行为契约。











