cancelacquire 是 aqs 中安全移除取消节点的核心方法:先设节点状态为 cancelled,再通过循环 cas 跳过前驱的取消节点并更新指针,不直接唤醒后继,依赖前驱在后续操作中懒清理,同时置空 thread 和 next 避免内存泄漏。

cancelAcquire 是 AQS(AbstractQueuedSynchronizer)中处理线程取消排队的核心方法,当线程在等待获取锁(如 acquireInterruptibly 或 tryAcquireNanos)过程中被中断或超时,就会触发该方法,将当前节点从同步队列中安全移除。
节点状态设为 CANCELLED 并断开前驱引用
方法首先将当前节点的 waitStatus 设为 CANCELLED(值为 1),这是唯一表示节点已失效的状态。随后尝试跳过所有状态为 CANCELLED 的前驱节点,找到第一个有效的前驱(pred),并将它的 next 指针指向当前节点的后继(next)。这个过程不加锁,但通过循环重试和 CAS 保证可见性与原子性。
- 如果前驱是 head,会唤醒其后继(可能触发后续节点的传播)
- 如果前驱不是 head 且状态为
SIGNAL或可设为SIGNAL,则不会立即唤醒,仅完成指针清理 - 若前驱本身已是
CANCELLED,会继续向前遍历,直到找到有效前驱或到达 head
后继节点的唤醒延迟由前驱负责
cancelAcquire 不直接唤醒后继节点,而是依赖前驱节点在后续操作(如 shouldParkAfterFailedAcquire 或 unparkSuccessor)中识别并跳过已取消节点。例如:当某个节点 pred 调用 shouldParkAfterFailedAcquire 时,若发现 pred.next 是 CANCELLED,就会用 CAS 将 pred.next 指向更后面的非取消节点——这种“懒清理”降低了并发修改队列的冲突概率。
- 取消节点的
thread字段会被置为null,避免内存泄漏和误唤醒 -
next字段也可能被置为null,加速 GC 回收 - 后继节点是否能及时被唤醒,取决于它前驱节点何时执行 park 判断逻辑
中断与超时场景下的调用路径差异
虽然都走 cancelAcquire,但触发时机略有不同:
-
acquireInterruptibly在检测到Thread.interrupted()后,抛出InterruptedException前调用 cancelAcquire -
tryAcquireNanos在剩余等待时间 ≤ 0 或检测到中断时,同样进入 cancelAcquire;注意它会在循环中反复检查超时,确保 cancelAcquire 只执行一次 - 无论哪种情况,节点一旦被标记为
CANCELLED,就不会再参与锁竞争或被唤醒
实际影响:队列碎片与唤醒可靠性
AQS 同步队列允许存在多个连续的 CANCELLED 节点,它们像“占位符”一样留在队列中,直到被前驱或后继的常规操作顺带清理。这虽带来轻微内存占用和遍历开销,但换来了更高的并发性能和更低的同步成本。
- 正常情况下,CANCELLED 节点会在数次 acquire/release 操作内被自然清理
- 极端高并发取消场景下,可能出现较长的取消链,但不会阻塞正常流程
- 应用层无需干预,AQS 内部机制已保障语义正确性:已取消节点绝不会获得锁,也不会干扰其他线程的唤醒顺序
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











