acquiresharedinterruptibly能响应中断,因其在入队前、park前后均检查thread.interrupted(),一旦检测到中断即抛interruptedexception并执行cancelacquire将节点waitstatus设为cancelled,使其逻辑出队但物理保留在链表中。

acquireSharedInterruptibly 为什么能响应中断
因为 acquireSharedInterruptibly 在进入等待前会主动检查线程中断状态,且在阻塞中被唤醒后会再次校验并抛出 InterruptedException,而不是仅靠设置中断标记后继续等待。
它和 acquireShared 的关键区别在于:前者调用 doAcquireSharedInterruptibly,后者调用 doAcquireShared;前者在 park 前后都做 Thread.interrupted() 检查,一旦发现中断就直接抛异常退出,不走正常入队完成后的“设 head + 传播唤醒”路径。
- 中断发生在尝试获取资源前 → 直接抛
InterruptedException,不入队 - 中断发生在已入队、尚未 park 前 → 在
shouldParkAfterFailedAcquire返回 false 后下一轮循环中检测到中断,抛异常,触发cancelAcquire(node) - 中断发生在 park 中(即线程已被挂起)→
parkAndCheckInterrupt()返回 true,外层循环捕获后抛异常,同样走cancelAcquire(node)
cancelAcquire 如何让节点“逻辑出队”
cancelAcquire 并不是把节点从链表物理删除,而是通过 CAS 修改其 waitStatus 为 CANCELLED(值为 1),并尝试跳过该节点,让前驱节点的 next 指向它的后继 —— 这就是 AQS 共享模式下“中断出队”的真实含义:节点仍留在链表中,但被标记为无效,后续遍历时会被跳过。
- 节点的
thread字段被置为null,防止被误唤醒 - 若当前节点是 tail,会尝试用 CAS 更新 tail 指针向前收缩
- 若前驱节点是 head 或处于
SIGNAL状态,会尝试唤醒后继(避免唤醒已取消节点) - 关键点:
cancelAcquire不修改prev/next的双向引用,只靠waitStatus == CANCELLED控制遍历行为
共享模式下 propagate > 0 时中断是否影响唤醒传播
不影响。只要 tryAcquireShared 返回正数(表示还有剩余资源),setHeadAndPropagate 就会尝试唤醒后继共享节点 —— 即使当前线程因中断退出,这个唤醒动作已在成功获取资源后立即触发。
但要注意:如果中断发生在 setHeadAndPropagate 执行中途(比如刚设完 head、还没走到唤醒逻辑),那传播就会终止;而该方法本身不响应中断,所以实际中只要 tryAcquireShared 成功返回正数,传播大概率已完成。
-
propagate > 0是唤醒后继的充分条件,与当前线程是否中断无关 - 中断只影响“当前节点是否继续参与队列调度”,不影响“已成功获取资源后对队列的副作用”
- 真正危险的是:中断发生在线程刚入队、还没来得及调用
tryAcquireShared时,此时无 propagate 可言,也就无传播可言
为什么 tryAcquireSharedNanos 抛出 InterruptedException 后不能简单重试
因为 tryAcquireSharedNanos 内部使用的是 doAcquireSharedNanos,它在超时或中断退出时同样调用 cancelAcquire 标记节点为 CANCELLED。若用户在外层捕获异常后手动重试 acquireSharedInterruptibly,会新建一个 Node 入队 —— 旧节点虽被取消,但链表中仍残留,可能增加遍历开销;更严重的是,若重试频繁,会堆积大量 CANCELLED 节点,拖慢同步队列清理效率。
- AQS 不提供“复用已取消节点”的机制,每次调用都新建 Node
- 大量
CANCELLED节点会迫使shouldParkAfterFailedAcquire频繁向前跳过,间接增加 CPU 开销 - 实践中应避免在高频路径上反复触发中断式获取,优先考虑超时策略或预判资源可用性
cancelAcquire 后节点仍在队列里,只是被跳过;而传播唤醒一旦触发,就与发起线程的生命周期解耦了。










