acquireQueued 方法是线程在 tryAcquire 失败后进入 CLH 队列等待的主循环逻辑,核心为“挂起—唤醒—再试”闭环:先由 shouldParkAfterFailedAcquire 判断是否挂起,true 时调用 LockSupport.park(this) 交由 OS 挂起线程(状态变为 WAITING),唤醒后仍需循环重试 tryAcquire,因唤醒仅表示“可能有机会”,不保证锁空闲;中断或正常 unpark 均会唤醒,但需用 Thread.interrupted() 清除并捕获中断态;waitStatus 动态维护,SIGNAL 表示“前驱释放时请 unpark 我”,PROPAGATE 用于共享模式唤醒链;CANCELLED 节点不被 unpark,其后继由前驱“踢出队列”重链接;所有状态流转均依赖 CAS 和精巧的竞态处理。

acquireQueued 方法到底做了什么
它不是加锁入口,而是线程在 tryAcquire 失败后,进入 CLH 队列等待时的主循环逻辑:挂起自己、等待前驱节点释放、被唤醒后重试获取锁。关键不在于“排队”,而在于“挂起—唤醒—再试”这个闭环。
线程怎么被挂起的:parkAndCheckInterrupt 的真实行为
挂起发生在 shouldParkAfterFailedAcquire 返回 true 后调用 LockSupport.park(this)。这不是阻塞在 synchronized 那种 JVM 层级的 monitor 上,而是直接交由操作系统线程调度器 suspend 当前线程——此时线程状态变为 WAITING(JVM 级别),且不持有任何锁资源。
-
park()返回后,interrupted标志不一定为true:可能是被正常 unpark,也可能是被中断;必须用Thread.interrupted()清除并捕获一次态 - 如果前驱节点在
park前已取消(ws == Node.CANCELLED),shouldParkAfterFailedAcquire会跳过挂起,直接 continue 循环重试,避免无效等待 - 挂起前没有做任何超时判断——这就是为什么
acquireQueued只用于不可中断/无超时场景(如ReentrantLock.lock())
被唤醒后为什么还要循环重试 acquire
唤醒只是通知“可能有机会了”,不代表锁一定空闲。比如多个线程同时被 unpark,只有第一个能成功 tryAcquire,其余仍需再次竞争。所以 acquireQueued 的核心结构是 for(;;) + if (tryAcquire(arg)) return true;。
- 唤醒来源有两个:前驱节点在
setHeadAndPropagate或unparkSuccessor中调用LockSupport.unpark(s.thread) - 即使被中断唤醒,也会先走一遍
tryAcquire:失败则继续 park;成功则忽略中断(但会在 finally 中补上selfInterrupt()) - 不会检查自己是否已被 cancel——取消逻辑由
cancelAcquire单独处理,且只在入队后、挂起前或被唤醒后显式触发
容易被忽略的 waitStatus 更新时机和传播性
waitStatus 不是静态标记,而是在每次循环中动态维护的状态信号。比如 Node.SIGNAL 表示“我挂起了,前驱释放时请 unpark 我”,但它只在 shouldParkAfterFailedAcquire 中被设置,并依赖前驱节点正确执行 compareAndSetWaitStatus(pred, ws, Node.SIGNAL)。
- 如果前驱节点在设置
SIGNAL前就完成了释放,当前节点可能错过唤醒——这就是为什么 AQS 要求前驱节点在修改 state 后,必须调用unparkSuccessor来兜底唤醒后继 -
PROPAGATE状态只出现在共享模式(如CountDownLatch),且仅在setHeadAndPropagate中被写入,它会主动向后继传播 unpark,形成“唤醒链” - 任意节点的
waitStatus被设为CANCELLED后,不会被其他节点 unpark,但它的后继会被前驱节点“踢出队列”重新链接

















