AQS通过双向链表实现FIFO同步等待队列,以Node节点封装线程、状态及前后指针,头节点为空虚节点,尾节点指向最新入队节点;新节点通过CAS原子插入队尾,确保FIFO顺序;仅前驱为head的节点可尝试获取资源,释放时唤醒首个有效后继;公平锁在入队前检查是否已有等待者以避免插队,非公平锁则允许直接抢占;取消节点被标记为CANCELLED并惰性清理,不参与唤醒。

AQS(AbstractQueuedSynchronizer)通过一个双向链表实现的 FIFO 同步等待队列(CLH 变种),来有序管理竞争资源失败而被阻塞的线程。它不直接使用传统 CLH 队列,而是用前驱/后继指针维护节点顺序,确保线程按入队先后顺序被唤醒,从而保障公平性(在公平模式下)和调度可预测性。
队列结构:Node 节点构成双向链表
AQS 内部以 Node 类定义队列节点,每个节点封装一个线程(thread 字段)、等待状态(waitStatus)、前驱(prev)和后继(next)引用。头节点(head)是虚节点,代表当前持有锁或正在执行的线程;尾节点(tail)指向最新入队的节点。新线程竞争失败时,会以 CAS 方式原子地插入到队列尾部——这是 FIFO 顺序的物理基础。
- 入队过程非阻塞:调用
addWaiter(Node.EXCLUSIVE)创建节点,并循环 CAS 设置tail,失败则重试 - 头节点始终空置:它不关联任何线程,仅作为唤醒逻辑的起点(如
unparkSuccessor(head)) - 节点状态用于控制行为:
WAITING表示正常等待,CANCELLED表示线程中断或超时被取消,会被后续清理
入队与出队:FIFO 的关键操作逻辑
线程获取同步状态失败(如 tryAcquire 返回 false)后,会进入 acquire 流程:先构造成 Node 加入队列,再自旋检查是否为次头节点、且前驱是否为 head,满足才尝试再次获取;否则调用 LockSupport.park() 挂起。这种“检查-挂起-唤醒”机制依赖队列顺序:
在调试 Java 后端 API 故障时使用此技能,尤其适用于以下任务:分析日志、跨 Controller、Service 和 Mapper 层追踪请求、定位异常根因、检查 SQL 执行或参数流转,以及生成简洁的诊断结论与修复方案。
- 只有前驱是 head 的节点才有资格争抢资源(即“轮到你了”),保证严格 FIFO 唤醒次序
- 释放操作(
release)成功后,会调用unparkSuccessor(head),唤醒 head 的第一个有效后继节点 - 被唤醒的节点在下一轮自旋中重新尝试
tryAcquire,成功则设置为新 head,失败则继续 park
公平性由队列顺序 + 获取逻辑共同保障
公平锁(如 ReentrantLock(true))在 tryAcquire 中会额外判断:当前线程是否排在队列最前面(即 !hasQueuedPredecessors())。若队列非空且自己不是首节点,则直接返回 false,强制入队——这避免了新来的线程“插队”,让等待时间最长的线程优先获得机会。
- 非公平锁跳过该检查,允许刚到来的线程直接尝试抢占,可能绕过队列中等待者
- 但无论公平与否,一旦入队,所有节点都遵循 FIFO 唤醒规则,不会出现“后入先出”或随机唤醒
- 队列本身不区分公平/非公平,差异只体现在“是否允许插队”这一入口判断上
节点清理:应对取消与异常场景
线程可能因中断(interrupt)、超时(tryAcquireNanos)或显式取消而退出等待。此时对应 Node 的 waitStatus 被设为 CANCELLED。AQS 在入队、出队、唤醒等多处检查并跳过已取消节点,还会触发 cleanQueue() 类似逻辑,将无效节点从链表中摘除,保持队列紧凑有效。
- 取消节点仍保留在队列中,但不再参与唤醒传播(
shouldParkAfterFailedAcquire会跳过它们) - 前驱节点在 unpark 后继前,会主动向后查找第一个非取消节点进行唤醒
- 这种惰性清理策略兼顾性能与正确性,避免每次操作都遍历全队列
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










