volatile 与 CAS 功能互补、缺一不可:volatile 保证可见性,使 CAS 能读到最新值;CAS 保证原子性,弥补 volatile 对复合操作的无力;二者共同支撑 Java 无锁并发可靠性。

volatile 和 CAS 不是“搭配使用”的工具,而是功能互补、缺一不可的底层协同机制。volatile 保证可见性,CAS 保证原子性;没有 volatile,CAS 读到的可能是过期值;没有 CAS,volatile 无法防止竞态修改——二者共同支撑起 Java 无锁并发的可靠性。
volatile 提供可见性保障,让 CAS 能“看到最新”
在多线程环境下,每个 CPU 核心都有自己的高速缓存。若一个变量未被 volatile 修饰,线程 A 修改后,线程 B 可能仍在读取自己缓存中的旧值,导致 CAS 比较失败或误判成功(比如 A→B→A 的 ABA 场景中更隐蔽)。volatile 强制每次读都从主内存加载,每次写都立即刷回主内存,并插入内存屏障,阻止指令重排序。这确保了 CAS 中的“读取当前值”这一步拿到的是全局最新状态。
- 典型体现:AQS 中的 state 字段被声明为
private volatile int state - AtomicInteger 内部 value 字段也是 volatile 修饰的,
get()方法直接返回该字段值 - 若去掉 volatile,CAS 的 compare 阶段可能基于 stale 值判断,整个原子更新逻辑失效
CAS 实现原子更新,弥补 volatile 的非原子缺陷
volatile 只保证单次读/写的可见性与有序性,但不保证复合操作的原子性。例如 i++ 包含“读-改-写”三步,即使 i 是 volatile,仍可能被其他线程打断。CAS 正是为解决这一问题而生:它把“读取当前值—比对预期值—写入新值”封装成一条 CPU 硬件级原子指令(如 x86 的 cmpxchg),由 JVM 通过 Unsafe 类调用完成。
- AtomicInteger 的
incrementAndGet()底层是循环调用compareAndSet,直到成功 - ReentrantLock 的
tryAcquire用 CAS 尝试将 state 从 0 改为 1,失败则自旋重试 - AtomicReferenceArray 对数组任意索引位置的更新,也是先读 volatile 元素引用,再 CAS 替换
二者组合构建无锁数据结构的核心契约
无锁(lock-free)不是“不用同步”,而是用 CAS + volatile 构建可预测、可终止的协作模型:线程不阻塞、不挂起,靠反复验证+重试推进状态。这个模型成立的前提,正是 volatile 让所有线程始终基于同一份“事实”做决策,CAS 让每一次状态跃迁要么全成功、要么全失败、绝不中间态。
立即学习“Java免费学习笔记(深入)”;
- ConcurrentLinkedQueue 使用 volatile 的 next 和 item 字段 + CAS 修改指针,实现无锁入队出队
- StampedLock 的乐观读模式依赖 volatile 版本号 + CAS 检查是否脏读
- 即使发生高竞争,线程也只消耗 CPU 自旋,避免线程调度开销和死锁风险
实际编码中需注意的边界细节
这套机制强大但有隐含约束,忽略易引发隐蔽 bug:
- 不能替代 synchronized 的代码块语义:CAS 只保护单个变量,多个变量联动更新需额外设计(如用 AtomicReference 包装对象整体)
- ABA 问题真实存在:volatile+CAS 无法感知中间变化,高风险场景应改用 AtomicStampedReference 或 AtomicMarkableReference
- 过度自旋耗 CPU:无锁不等于零成本,竞争激烈时建议结合 Thread.onSpinWait() 或退避策略
- volatile 不保证初始化安全:对象构造未完成就发布,即使字段 volatile,其他线程仍可能看到部分构造状态


















