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 指向更后面的非取消节点——这种“懒清理”降低了并发修改队列的冲突概率。
你接手一个需求时,是不是经常这样:"改一个小字段 → 结果把不相关的代码也改了"、"需求说得很模糊 → 做着做着发现跑偏了"、"数据库加个字段 → 上线才发现漏了数据兼容"、"做了个新功能 → 一个月后自己都忘了当时怎么设计的"、"写到一半被中断 → 再回来不知道从哪继续";这个 Skill 就是专门解决这些问题的。 它会自动探测你的项目环境(JDK、Spring Boot、ORM、数据库、前端框架),记在
- 取消节点的
thread字段会被置为null,避免内存泄漏和误唤醒 -
next字段也可能被置为null,加速 GC 回收 - 后继节点是否能及时被唤醒,取决于它前驱节点何时执行 park 判断逻辑
中断与超时场景下的调用路径差异
虽然都走 cancelAcquire,但触发时机略有不同:
立即学习“Java免费学习笔记(深入)”;
-
acquireInterruptibly在检测到Thread.interrupted()后,抛出InterruptedException前调用 cancelAcquire -
tryAcquireNanos在剩余等待时间 ≤ 0 或检测到中断时,同样进入 cancelAcquire;注意它会在循环中反复检查超时,确保 cancelAcquire 只执行一次 - 无论哪种情况,节点一旦被标记为
CANCELLED,就不会再参与锁竞争或被唤醒
实际影响:队列碎片与唤醒可靠性
AQS 同步队列允许存在多个连续的 CANCELLED 节点,它们像“占位符”一样留在队列中,直到被前驱或后继的常规操作顺带清理。这虽带来轻微内存占用和遍历开销,但换来了更高的并发性能和更低的同步成本。
- 正常情况下,CANCELLED 节点会在数次 acquire/release 操作内被自然清理
- 极端高并发取消场景下,可能出现较长的取消链,但不会阻塞正常流程
- 应用层无需干预,AQS 内部机制已保障语义正确性:已取消节点绝不会获得锁,也不会干扰其他线程的唤醒顺序

















