前言
AQS 的 cancelAcquire
方法值得花一篇文章来写,因为虽然代码不多,但是涉及到的情况不少。
在 AQS 源码中该方法共被 6 个地方调用(3 个独占锁,3 个共享锁):acquireQueued
/ doAcquireInterruptibly
/ doAcquireNanos
doAcquireShared
/ doAcquireSharedInterruptibly
/ doAcquireSharedNanos
该方法目的为:获取过程抛出异常后,取消正在进行的获取尝试。
不考虑 Condition 的前提下,该方法是唯一会将 sync queue 中 Node 的 waitStatus 置为 CANCELLED 的方法。
穷举所有 case
从该方法最后部分 if (node == tail && ...
和 if (pred != head && ...
两句可以看出,该方法进行操作的三个不同的大前提条件为:
- node 是 tail
- node 不是 tail && pred 不是 head
- node 不是 tail && pred 是 head
我们先不考虑这些 case 如何达成,本文我们暂时只关注:穷举出所有 case 并看每种 case 的最终结果。
注:下文中 normalNode 代表 waitStatus <= 0 的节点;cancelNode 代表 waitStatus 为 CANCELLED 的节点。
- node 是 tail + pred 是 head
head <=> [node]
head <=> cancelNode <=> [node] - node 是 tail + pred 不是 head
head <=> normalNode <=> [node]
head <=> normalNode <=> cancelNode <=> [node] - node 不是 tail && pred 不是 head + node 后为 normalNode
head <=> normalNode <=> [node] <=> normalNode
head <=> normalNode <=> cancelNode <=> [node] <=> normalNode - node 不是 tail && pred 不是 head + node 后为 cancelNode
head <=> normalNode <=> [node] <=> cancelNode
head <=> normalNode <=> cancelNode <=> [node] <=> cancelNode - node 不是 tail && pred 是 head + node 后为 normalNode
head <=> [node] <=> normalNode
head <=> cancelNode <=> [node] <=> normalNode - node 不是 tail && pred 是 head + node 后为 cancelNode
head <=> [node] <=> cancelNode
head <=> cancelNode <=> [node] <=> cancelNode
(在下文中会看到,其实 5 和 6 是可以合并的)
node 是 tail + pred 是 head
case1.1
head <=> [node]
case1.2
head <=> cancelNode <=> [node]
何时清除 node
当 head 节点变更时,即前置线程释放锁,sync queue 中第一个排队线程获取锁后调用了 setHead
方法和 p.next = null
后。
node 是 tail + pred 不是 head
case2.1
head <=> normalNode <=> [node]
case2.2
head <=> normalNode <=> cancelNode <=> [node]
何时清除 node
当排队线程陆续获取锁直到 pred 变为 head,然后(同 case1)当 head 节点变更时,即前置线程释放锁,sync queue 中第一个排队线程获取锁后调用了 setHead
方法和 p.next = null
后。
node 不是 tail && pred 不是 head + node 后为 normalNode
case3.1
head <=> normalNode <=> [node] <=> normalNode
case3.2
head <=> normalNode <=> cancelNode <=> [node] <=> normalNode
何时清除 node
当别的线程调用 shouldParkAfterFailedAcquire
或 cancelAcquire
方法时,会调整其遍历节点的指针,(同 case2)当排队线程陆续获取锁直到 node 后的 normalNode 变为 head,然后当 head 节点变更时,即前置线程释放锁,sync queue 中第一个排队线程获取锁后调用了 setHead
方法和 p.next = null
后。
node 不是 tail && pred 不是 head + node 后为 cancelNode
case4.1
head <=> normalNode <=> [node] <=> cancelNode
case4.2
head <=> normalNode <=> cancelNode <=> [node] <=> cancelNode
何时清除 node
当 node 后面的 cancelNode 后面有了 normalNode,(同 case3)当别的线程调用 shouldParkAfterFailedAcquire
或 cancelAcquire
方法时,会调整其遍历节点的指针,当排队线程陆续获取锁直到 node 后的 normalNode 变为 head,然后当 head 节点变更时,即前置线程释放锁,sync queue 中第一个排队线程获取锁后调用了 setHead
方法和 p.next = null
后。
case5 + case6
case5:node 不是 tail && pred 是 head + node 后为 normalNode
case6:node 不是 tail && pred 是 head + node 后为 cancelNode
可以发现,其实该方法的操作不会涉及到 node 后所跟的节点,因此,无论 node 后跟的是 normalNode 或 cancelNode,结果其实都是一样的。
case5.1 + case6.1
head <=> [node] <=> normalNode
head <=> [node] <=> cancelNode
case5.2 + case6.2
head <=> cancelNode <=> [node] <=> normalNode
head <=> cancelNode <=> [node] <=> cancelNode
何时清除 node
- 当 node 后是 normalNode 时,会唤醒线程去获取锁,获取到锁后该节点变为头节点,注意此时 node 其实还没有彻底清除掉,还需要此后(同 case1)当 head 节点变更时,即前置线程释放锁,sync queue 中第一个排队线程获取锁后调用了
setHead
方法后 - 当 node 后是 cancelNode 时,啥也不会做,因为唤不醒该线程,只能等(同 case4)当 node 后面的 cancelNode 后面有了 normalNode,当别的线程调用
shouldParkAfterFailedAcquire
或cancelAcquire
方法时,会调整其遍历节点的指针,当排队线程陆续获取锁直到 node 后的 normalNode 变为 head,然后当 head 节点变更时,即前置线程释放锁,sync queue 中第一个排队线程获取锁后调用了setHead
方法和p.next = null
后
小结
该方法无论什么条件,都会做的事是:
- pred 指向从 node 往前遍历的第一个状态不为 CANCELLED 的节点
- node.prev 指向 pred
- node 的 waitStatus 置为 CANCELLED
此后,当条件为:
- node 是 tail:将 tail 指向 pred;将 pred.next 指向 null
- node 不是 tail && pred 不是 head:将 pred 的 waitStatus 置为 SIGNAL;如果 node.next 的 waitStatus 不为 CANCELLED,将 pred.next 指向 node.next
- node 不是 tail && pred 是 head:调用 unparkSuccessor 方法唤醒 node 的后继节点
可以发现该方法很多结果最终都是修改 next 指针而保留 prev 指针,我认为这是因为考虑到还有很多方法都采用从 tail 往前遍历,而遍历时会跳过 waitStatus 为 CANCELLED 的节点。因此在这里只要修改节点的 waitStatus 以及 next 指针就足够了,早晚都会有别的方法会通过遍历 / setHead
/ p.next = null
去将 waitStatus 为 CANCELLED 的节点从 sync queue 中完全断开!