题目:有了 CAS 为啥还需要 volatile?
难度:medium
标签:java/volatile, general/knowledge
评分:⭐⭐⭐⭐☆
CAS 跟 Volatile 不是一个层面的东西,它们处理的事情也不是一个。Volatile 是处理可见性和避免指令重排的,而 CAS 它是一个并发方面的处理方式,它的核心是比较交换,并且在 Java 中,CAS 其实是基于 Volatile 去实现的,二者存在依附关系。因为 CAS 进行比较的时候,需要通过 volatile 去保证可见性,确保比较的对象是当前的值,来最终实现 CAS 比较交换的功能。CAS 跟 Voltaio 相互之间并不是竞争关系,是依附关系。
优点:抓住核心洞察——依附而非竞争关系;正确说明 CAS 基于 volatile 实现;简洁准确
不足:未举 AtomicInteger 源码例子;未展开 DCL 单例为什么两者都要;未说"互补关系"(volatile 管可见性,CAS 管原子性)
补充:依附关系链 volatile 屏障→MESI→CAS 读真实值;互补关系 volatile 管单操作可见、CAS 管复合操作原子;AtomicInteger.value 就是 volatile
核心考点定位:能否说清"依附 + 互补"两条关系链,而不是把二者对立。
知识点拆解:
AtomicInteger 的核心字段是 private volatile int value;lock 前缀指令 → CPU 触发 MESI 失效广播 → 其他核下次必须读主存 → CAS 才能读到真实值i++ 用 volatile 不行(读-改-写三步,volatile 只保证每步的可见性)i++ 用 CAS 就行(compareAndSet 把三步打包成一个原子操作)AtomicInteger.get() 不加 CAS 也能正确读,因为它就是 volatile 读 private volatile static Singleton s; // volatile 防止指令重排
public static Singleton get() {
if (s == null) { // volatile 读,保证可见性
synchronized (Singleton.class) {
if (s == null) { // 二次检查
s = new Singleton(); // CAS 或 volatile 写
}
}
}
return s;
}
new 被重排成"分配内存→赋引用→初始化"导致其他线程看到半构造实例按「底层原理 → 设计权衡 → 工程实践」递进排列。
考察方向:
延伸递进面试题:
面试官您好,CAS 和 volatile 不是竞争关系,而是依附 + 互补的关系,这是我觉得这道题的关键点。
先说依附关系。在 Java 里,CAS 其实就是基于 volatile 实现的。最典型的例子就是 AtomicInteger,它里面的核心字段是 private volatile int value,没有这个 volatile,CAS 根本没法工作。为什么?因为 CAS 要做的是"比较并交换",这个比较动作必须先读到主存的真实值。如果没有 volatile 的内存屏障,JIT 会把值缓存在寄存器里,永远拿不到最新值,CAS 的比较就失去了意义。所以从底层链条看:volatile 写时加 lock 前缀指令 → CPU 触发 MESI 协议让其他核缓存失效 → 下次读必须从主存取 → CAS 才能读到真实值。CAS 是 volatile 的"消费者"。
再说互补关系。volatile 保证的是单条读/写操作的可见性和有序性;CAS 保证的是读-改-写复合操作的原子性。一个管"看",一个管"改",谁也替代不了谁。
最经典的反例是 i++。用 volatile 修饰 i,i++ 仍然不安全,因为 i++ 是三步:读 i、i+1、写回。volatile 只能保证每步操作的可见性,没法保证三步作为一个整体的互斥。这里必须用 AtomicInteger,它的 incrementAndGet 方法内部就是用 CAS 把这三步打包成一个原子操作。反过来,如果只是想读一个值,AtomicInteger.get() 其实就是个 volatile 读,不需要 CAS。
还有一个典型场景是双重检查锁单例,这里 volatile 和 CAS(或 synchronized)必须同时用:
private volatile static Singleton s;
public static Singleton get() {
if (s == null) {
synchronized (Singleton.class) {
if (s == null) {
s = new Singleton();
}
}
}
return s;
}
synchronized 保证"判断+赋值"的原子性,但 new 指令会被 JVM 重排成"分配内存→赋引用→初始化构造器",如果不加 volatile,另一个线程可能看到"已经赋引用但还没初始化"的半构造实例,直接用就 NPE。所以 volatile 在这里不是可选的,是必须的。
总结一句话:volatile 是 CAS 的底层基石,CAS 是 volatile 的能力延伸,两者是配合关系不是替代关系。
这就是我对这道题的理解。