AQS 中 CAS 更新 tail 指针的原子性由 CPU 硬件指令(如 CMPXCHG)保障,volatile 保证可见性与最新值读取,Unsafe.compareAndSwapObject 一次性完成比较、判断、交换三步,失败则自旋重试,结合 prev 延迟设置与状态校验确保队列一致性。

AQS 中用 CAS 更新 tail 指针能保证原子性,核心在于底层调用 Unsafe.compareAndSwapObject 方法,借助 CPU 提供的原子指令(如 x86 的 CMPXCHG)直接在硬件层面完成“比较-交换”操作。
CAS 更新 tail 的本质是单次内存位置的原子写入
AQS 的 tail 字段被声明为 volatile Node tail,这带来两层保障:
-
可见性:所有线程对
tail的读写都直接作用于主内存,不依赖本地缓存; -
原子性基础:
volatile本身不保证复合操作原子,但为 CAS 提供了正确读取当前值的前提。
真正实现原子更新的是这段典型逻辑(见 enq() 方法):
立即学习“Java免费学习笔记(深入)”;
你接手一个需求时,是不是经常这样:"改一个小字段 → 结果把不相关的代码也改了"、"需求说得很模糊 → 做着做着发现跑偏了"、"数据库加个字段 → 上线才发现漏了数据兼容"、"做了个新功能 → 一个月后自己都忘了当时怎么设计的"、"写到一半被中断 → 再回来不知道从哪继续";这个 Skill 就是专门解决这些问题的。 它会自动探测你的项目环境(JDK、Spring Boot、ORM、数据库、前端框架),记在
if (compareAndSwapObject(null, tailOffset, expect, update)) {
// 成功:tail 已从 expect 变为 update
}其中 tailOffset 是 tail 字段在对象内存中的偏移量,由 Unsafe.objectFieldOffset 静态计算得出。这个 CAS 调用一次完成三个动作:
- 读取
tail当前值; - 判断是否等于预期值
expect; - 如果相等,将
tail设为新值update; - 整个过程不可中断、不可分割。
为什么不会出现两个线程同时成功?
因为现代 CPU 的 CAS 指令是总线锁定或缓存一致性协议(如 MESI)保障下的原子操作。即使多核并发执行,硬件会确保同一内存地址上的 CAS 只有一个能成功——失败者看到的 tail 值已变,需重试(AQS 中体现为自旋重试入队)。
实际入队时的配合机制
- 新节点入队不是“先改 tail 再连 prev”,而是:
- 先用 CAS 尝试把
tail从旧节点指向新节点; - 成功后,再设置新节点的
prev指向原tail(这步不要求原子,因prev只被前驱和自身访问,且后续唤醒逻辑有状态校验);
- 先用 CAS 尝试把
- 即使
prev设置稍晚,只要tail更新成功,其他线程通过tail能找到新节点,再顺着prev回溯即可,不影响队列结构一致性。
关键点总结
- CAS 更新
tail是单字段、单指令、单内存地址的操作,由硬件保证原子; -
volatile保证每次 CAS 读到的是最新值,避免 stale value 导致误判; - AQS 不依赖锁,靠“失败重试 + 状态驱动”应对 CAS 竞争,符合乐观并发设计思想;
- 所有基于
Unsafe.compareAndSwapXxx的操作(包括state更新)共享同一套底层机制,统一构成 JUC 并发安全的基石。

















