volatile仅保障可见性与禁止重排序,无法提供原子性;CAS才是无锁操作的原子性基石,二者分工明确、缺一不可,共同支撑ConcurrentLinkedQueue等高性能无锁队列的正确实现。

Java 中不能直接用 volatile 和 CAS 共同“实现”一个完整的无锁队列——因为 volatile 本身不提供原子性,而 CAS(如 Unsafe.compareAndSwapInt)才是无锁操作的核心;volatile 只是辅助保证可见性和禁止重排序,为 CAS 正确性打基础。真正高性能的无锁队列(如 ConcurrentLinkedQueue)底层依赖的是 Unsafe 的 CAS 操作 + volatile 字段语义,而非简单组合两者就能写出正确队列。
volatile 的作用:不是同步,而是建立 happens-before 和内存可见性
volatile 关键字在无锁结构中主要做三件事:
- 确保对 volatile 字段的读写具有全局可见性(一个线程写,其他线程立刻看到最新值)
- 禁止编译器和 CPU 对 volatile 读写做重排序(比如不能把后续普通写提前到 volatile 写之前)
- 与 CAS 配合时,使 CAS 成功后的状态变更对其他线程可感知(例如更新 tail 指针后,其他线程能基于新 tail 继续入队)
注意:volatile 不能保证复合操作的原子性(如 count++),所以它永远不能替代 CAS 来完成“读-改-写”这类操作。
CAS 是无锁队列的原子操作基石
所有真正的无锁逻辑都靠 CAS 实现,例如:
立即学习“Java免费学习笔记(深入)”;
- 入队时用
tail.compareAndSet(old, new)尝试更新尾节点指针 - 出队时用
head.compareAndSet(old, new)尝试更新头节点指针 - 当 CAS 失败(说明被其他线程抢先修改),就重试(loop until success)或回退处理
Java 标准库中 AtomicReference、AtomicInteger 等类封装了 Unsafe 的 CAS 调用,是构建无锁结构最常用的工具。自己用 Unsafe 直接操作风险高、可移植性差,不推荐。
典型模式:volatile 引用 + 原子引用 CAS
以单生产者单消费者(SPSC)简化队列为例,关键字段通常这样声明:
private volatile Node head; // volatile 保证 head 读取最新 private volatile Node tail; private final AtomicReference<Node> tailRef = new AtomicReference<>(); // 也可用 AtomicReference 替代 volatile + 手动 CAS
实际入队逻辑类似:
- 先读取当前
tail(volatile 读,获取最新尾节点) - 构造新节点并设置其
next字段(可能也是 volatile 字段) - 用
tailRef.compareAndSet(currentTail, newNode)原子更新尾指针 - 若失败,说明有竞争,重新读 tail 并重试
这里 volatile 保障了“读 tail”这一步拿到的是全局最新值;CAS 则保障“更新 tail”这一步是原子的——二者分工明确,缺一不可。
为什么不要自己从零手写通用无锁队列
看似简单的链表队列,实际要处理大量边界情况:
- ABA 问题(虽然多数队列通过节点不可变或版本号缓解)
- 内存释放安全(Java GC 缓解了部分问题,但仍有循环引用、过早回收等隐患)
- 虚假失败(CAS 失败不等于业务失败,需判断是否真冲突)
- tail/head 滞后问题(tail 可能远落后于实际尾部,需要追赶逻辑)
ConcurrentLinkedQueue 经过多年演进和大量测试,已高度优化且正确。除非有极致性能定制需求(如 LMAX Disruptor 场景),否则应优先使用成熟实现,而非自行基于 volatile + CAS 拼装。



















