AQS同步队列采用懒加载方式初始化哨兵节点,即首次线程入队时通过enq方法以CAS+自旋创建head和tail指向同一空节点,该节点thread为null、waitStatus为0,仅作占位,后续唤醒从head.next开始。

Java 中 AQS(AbstractQueuedSynchronizer)的同步队列在初始化时并不立即创建哨兵节点,而是采用懒加载方式——即首次发生线程入队操作时才创建头节点(head)和尾节点(tail),并让它们指向同一个新分配的空节点(即哨兵节点)。
哨兵节点是空节点,不绑定任何线程
AQS 的 CLH 风格队列中,头节点(head)是一个“虚节点”或“哨兵节点”,它本身不表示任何等待线程,仅作为队列头部占位符。该节点的 thread 字段为 null,waitStatus 默认为 0。只有当第一个线程尝试获取锁失败而需要入队时,AQS 才会执行:
- 调用
enq(node)方法,内部通过自旋 + CAS 确保 tail 初始化; - 若
tail == null(即队列未初始化),则先用 CAS 创建一个空节点作为 tail; - 同时将 head 也设为该节点,完成哨兵节点的建立。
初始化过程由 enq 方法保证线程安全
因为 head 和 tail 是 volatile 字段,且初始化依赖 CAS 操作,所以即使多个线程并发触发入队,enq 内部的自旋逻辑也能确保只有一个线程成功设置初始 tail(进而 head),其余线程随后基于已初始化的 tail 继续追加。关键代码逻辑如下:
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // 队列为空,需初始化
if (compareAndSetHead(new Node())) // 创建空节点设为 head
tail = head; // head 和 tail 指向同一哨兵节点
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
head 哨兵节点不会被移除,但可能被跳过
一旦初始化完成,head 始终存在且通常保持为哨兵节点。后续唤醒操作(如 unparkSuccessor)会从 head.next 开始找第一个非取消状态的后继节点来唤醒,因此 head 本身只是结构所需,并不参与实际线程调度。如果 head.next 为 null 或已被取消,AQS 会向后遍历直到找到有效节点。
立即学习“Java免费学习笔记(深入)”;
没有显式“初始化方法”,全靠首次入队触发
AQS 构造器不做队列初始化,head 和 tail 初始值均为 null。只有当 acquire 流程中尝试 addWaiter 失败(因 tail 为 null)后回落到 enq,才会真正构建哨兵节点。这种设计减少了无竞争场景下的对象分配和内存开销。


















