volatile 与 CAS 必须配合使用:volatile 保证可见性使 CAS 读到最新值,CAS 通过硬件指令实现原子更新;二者缺一不可,共同构成 Java 无锁并发基石。

volatile 和 CAS 配合,是 Java 无锁并发的底层基石。单独用 volatile 只能保证可见性和禁止重排序,不能保证原子性;单独用 CAS 无法感知变量是否已被其他线程修改——它依赖每次读取的都是最新值。二者缺一不可:volatile 确保 CAS 能“看见”最新值,CAS 则在 volatile 基础上完成原子更新。
volatile 是 CAS 正确工作的前提
AtomicInteger、AtomicBoolean 等原子类的内部 value 字段,都用 volatile 修饰。例如:
private volatile int value;
这样设计不是为了“让 value 线程安全”,而是为了让 compareAndSet() 方法中读到的值,一定是主内存中的最新值,而不是某个线程本地工作内存里的过期副本。没有 volatile,CAS 可能拿旧值去比,导致误判成功(比如 A 线程刚把值从 1 改成 2,B 线程却还拿着 1 去比,结果把 2 错误覆盖为新值)。
CAS 操作必须是原子的,靠 CPU 指令保障
CAS 不是 Java 层面的逻辑,而是由 JVM 调用 Unsafe 类,最终转为一条硬件指令完成:
- X86 架构下是 lock cmpxchg 指令
- 该指令执行时会锁定缓存行或总线(取决于 CPU 实现),确保“读-比-写”三步不可分割
- 整个过程不会被线程调度打断,天然原子
所以你看到的 AtomicInteger.incrementAndGet(),本质就是循环调用 CAS:读当前值 → 加 1 → 尝试用 CAS 写回。失败就重试,直到成功。
典型无锁模式:自旋 + volatile + CAS
一个标准的无锁计数器实现逻辑如下:
分析 Java Spring Boot 后端 Controller 代码,自动生成 Markdown 格式的接口文档。当用户说“生成 Java 接口文档”、“生成 API 文档”、“从 Spring 代码生成文档”时触发。
private volatile int count = 0;
public void increment() {
int expect;
do {
expect = count; // volatile 读:强制从主存取
} while (!compareAndSwap(expect, expect + 1)); // CAS 失败则重试
}
关键点:
- 每次循环开头的 count 读取必须是 volatile 读,否则可能一直读到旧值,陷入死循环
- CAS 方法(如 Unsafe.compareAndSwapInt)内部会再次读取 count 当前值,与 expect 比较——这一步也依赖 volatile 的语义支撑
- 整个结构不阻塞线程,适合低竞争、多核场景;高竞争时重试开销变大,需考虑 LongAdder 等优化方案
为什么不能只用 volatile?
volatile 无法解决“读-改-写”这类复合操作的竞态问题。例如:
if (running) { doWork(); } —— 这里 volatile 保证 running 的变化可见,没问题;
counter++; —— 这是三步:读 counter、加 1、写回。volatile 只能保证每一步的读/写可见,但三步之间仍可被其他线程插入修改,结果丢失。
所以:volatile 是 CAS 的“眼睛”,CAS 是 volatile 的“手”。一个负责看清,一个负责稳准快地动手。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










