acquire方法是三段式协作流程:先tryAcquire原子抢锁,失败则addWaiter入队(含快速CAS与enq重试),再由acquireQueued自旋+阻塞;全程依赖CAS和volatile,需确保state更新与owner线程同步。

acquire 方法整体流程:不是原子操作,而是三段式协作
acquire 看似一个方法,实际是三个关键动作的组合:先 tryAcquire 尝试抢锁;失败则调用 addWaiter 入队;再由 acquireQueued 负责自旋 + 阻塞。这三步之间没有锁保护,全靠 CAS 和 volatile 语义维系正确性。
常见错误现象是误以为 acquire 内部会“自动处理排队逻辑”,结果在子类重写 tryAcquire 时漏掉对 state 的原子更新(比如用了普通赋值而非 compareAndSetState),导致后续线程看到脏状态,反复入队或跳过唤醒。
-
tryAcquire必须是原子判断+原子修改,不能拆成“读 state → 判断 → 写 state”三步 -
addWaiter的快速路径(直接 CAS tail)失败后,一定会走enq的无限重试逻辑,所以不会丢节点 -
acquireQueued中的自旋不是空转,它只在前驱是 head 时才再次调用tryAcquire,避免惊群
addWaiter 与 enq:为什么需要两层入队逻辑
快速路径(addWaiter 直接 CAS tail)在高并发下大概率失败——多个线程同时看到同一个 tail,都试图把它指向自己,只有一个成功。失败者立刻进入 enq,后者用 for(;;) + CAS 自旋确保节点最终落位。
这里容易踩的坑是:认为 enq 只在队列为空时触发。实际上只要 CAS tail 失败就会进 enq,哪怕队列已有上百个节点。这也是 AQS 能支撑高并发排队的关键设计。
- 第一次
enq会初始化 dummyhead节点(new Node()),此时head == tail - 后续所有节点都通过
enq插入,保证 prev/next 双向链接完整,为唤醒传播打基础 - 注意
Node构造时传入的是Thread.currentThread(),不是当前锁持有者
acquireQueued 中的阻塞时机:SIGNAL 状态是唤醒前提
线程真正被挂起(LockSupport.park)前,必须先把自己前驱节点的 waitStatus 设为 SIGNAL(-1)。这个状态是“我后面有人等着,你释放锁后请唤醒我”的信号。
如果前驱节点是 CANCELLED(1),acquireQueued 会跳过它继续往前找,直到找到一个有效前驱。这就是为什么队列里可能堆积已取消的节点,但不阻塞后续唤醒。
-
shouldParkAfterFailedAcquire是状态修正器:把前驱的waitStatus从 0 改成SIGNAL,或跳过CANCELLED - 只有当
shouldParkAfterFailedAcquire返回 true,才会执行parkAndCheckInterrupt - 阻塞前没设好
SIGNAL,释放锁时unparkSuccessor就找不到该唤醒谁
唤醒不是“通知下一个”,而是“唤醒第一个可运行节点”
release 流程中,unparkSuccessor 并不无脑唤醒 head.next。它会从 tail 往前遍历,跳过所有 CANCELLED 节点,找到离 head 最近的一个非取消节点,然后 unpark 它。
这个反向遍历设计是为了规避并发修改导致的 next 指针为空问题(因为 cancelAcquire 会清空节点的 next 引用)。但代价是:唤醒不是严格 FIFO,极端情况下可能略过中间节点。
- 被唤醒的线程回到
acquireQueued的 for 循环,重新检查前驱是否为 head - 如果此时 head 已变(比如有新节点插入且前驱恰好是 head),它可能立即抢到锁,无需再 park
- 如果唤醒后发现前驱不是 head,它会再次尝试设
SIGNAL并 park —— 这就是所谓的“等待重入”
最易被忽略的一点:整个流程里没有任何地方显式维护“当前持有锁的线程是谁”,state 值和 exclusiveOwnerThread(来自父类 AbstractOwnableSynchronizer)是分开管理的。子类实现 tryAcquire 时,必须同步调用 setExclusiveOwnerThread,否则 isHeldExclusively 和调试诊断会失效。










