SIGNAL 状态是唤醒后继节点的唯一可靠依据,因其代表前驱节点承诺在释放锁时 unpark 当前节点;其他状态无此语义,且需通过 shouldParkAfterFailedAcquire() 主动设置或跳过已取消节点来保障该契约。

为什么 SIGNAL 状态是唤醒后继节点的唯一可靠依据
线程在同步队列中挂起前,必须确保它的前驱节点 waitStatus 是 SIGNAL(-1),否则调用 LockSupport.park() 后可能永远无法被唤醒。这不是约定,而是 AQS 的硬性协作契约:只有 SIGNAL 才代表“我释放锁时,会负责 unpark 你”。其他状态如 0、CANCELLED 或 CONDITION 都不具备这个语义。
常见错误现象:ThreadA 入队后发现前驱是 head 且其 waitStatus == 0,直接 park → 卡死;或前驱已 CANCELLED 却未跳过,导致唤醒链断裂。
-
shouldParkAfterFailedAcquire()会主动把前驱从0改成SIGNAL,但前提是该前驱还“活着”(非CANCELLED) - 如果前驱是
CANCELLED,当前节点会继续往前找第一个非取消节点,并尝试将其设为SIGNAL - 若遍历到
head仍找不到可用前驱(比如 head 自身刚被设为CANCELLED),则当前节点需自旋重试获取锁,不 park
signal() 调用后,SIGNAL 状态如何在出队过程中被实际消费
当线程成功 release 锁并执行出队逻辑(如 unparkSuccessor()),它只检查自己节点的 next 是否存在且 waitStatus ;真正触发唤醒的是:当前节点(即将出队的 head)的 <code>waitStatus 必须是 SIGNAL,才说明“我承诺过要唤醒后继”。如果不是,就跳过唤醒——这正是防止误唤醒的关键守门机制。
根据 package.json 中的 engines 字段自动检测 Node.js 版本需求,未安装 nvm 时自动安装并切换至对应版本。支持 Windows、macOS、Linux。适用于 Node.js 项目开发、版本切换和环境配置。
典型场景:head 节点释放锁前,其 waitStatus 是 SIGNAL,于是调用 unpark(s.next.thread);s 被唤醒后,在 acquireQueued() 中尝试获取锁,成功则成为新 head,原 head 被移除。
- 若 head 的
waitStatus != SIGNAL(比如是0或PROPAGATE),unparkSuccessor()不会唤醒任何后继,除非是共享模式下走setHeadAndPropagate() -
SIGNAL是独占模式下“一对一唤醒”的唯一开关,不可被绕过或忽略 - 注意:
unparkSuccessor()并不校验后继节点是否有效,它只负责 unpark;后续由被唤醒线程自己检查前驱是否已出队、state 是否可获取
容易被忽略的 SIGNAL 状态维护细节
很多人以为 SIGNAL 是入队时设置的,其实不是。它是在首次尝试 acquire 失败后,由当前线程**主动设置前驱**得到的。这意味着:一个节点的 SIGNAL 状态永远属于它的前驱,而不是自己;而自己能否被唤醒,完全取决于前驱是否真的持有 SIGNAL 并成功释放。
- 节点刚入队时
waitStatus == 0,此时它不能 park,必须先调用shouldParkAfterFailedAcquire()尝试改前驱状态 - 如果前驱正在 cancel(
waitStatus == CANCELLED),当前节点会帮助清理链表,并继续向前找,这个过程可能跨越多个取消节点 - 极端情况:head 本身是
CANCELLED,但 head.next 是正常节点 → 此时 head.next 无法靠 head 唤醒,必须依赖外部线程再次 release 或内部自旋竞争
共享模式下 SIGNAL 和 PROPAGATE 的分工边界
在 ReentrantReadWriteLock 或 Semaphore 中,SIGNAL 依然存在,但它只用于“通知下一个节点可以开始竞争”,而真正实现批量唤醒的是 PROPAGATE。也就是说:SIGNAL 解决“谁来启动唤醒”,PROPAGATE 解决“唤醒之后要不要继续传下去”。两者不互斥,但职责分明。
- 头节点完成共享获取后,若
h.waitStatus == PROPAGATE或propagate > 0,就会调用unparkSuccessor(h)继续唤醒 - 但这个唤醒动作的前提,仍是 h 的后继节点已处于可被 unpark 状态 —— 即它的前驱(h)曾成功设过
SIGNAL或已进入传播流程 - 不要试图复用
SIGNAL实现共享唤醒,那是PROPAGATE的专属职责;混淆二者会导致唤醒遗漏或重复

















