CAS 到底是怎么保证原子性的?从 Unsafe 到 VarHandle 的演进
面试常考:CAS 原理、ABA 问题、AtomicStampedReference、VarHandle
一、从 i++ 的线程安全问题说起
java
private int count = 0;
// 多线程下 count++ 不安全,因为它是 读→加→写 三步
// 方案1: synchronized --- 重,内核态切换
// 方案2: AtomicInteger --- 轻,基于 CAS 无锁
private AtomicInteger count = new AtomicInteger(0);
count.incrementAndGet(); // CAS 无锁原子操作
AtomicInteger 的 incrementAndGet 底层就是 CAS。CAS 是什么?它是怎么做到无锁原子操作的?
二、CAS 原理
CAS (Compare And Swap):比较并交换。三个参数:内存地址 V、期望值 A、新值 B。当 V 的值等于 A 时,将 V 更新为 B,否则不做操作。整个操作是原子的。
kotlin
CAS(V, expectedValue, newValue):
┌──────────────────────────────────────────────┐
│ if (V == expectedValue) { │
│ V = newValue; │
│ return true; │
│ } else { │
│ return false; // 期望值不匹配,重试 │
│ } │
└──────────────────────────────────────────────┘
↑ 这三步在硬件层面是一条指令,不可分割
硬件支持
x86 上 CAS 对应 cmpxchg 指令,多核下加 lock 前缀保证缓存一致性:
css
; x86 cmpxchg 指令
; LOCK CMPXCHG [mem], reg
; 如果 EAX == [mem],则 [mem] = reg,否则 EAX = [mem]
; lock 前缀确保多核下原子性,通过 MESI 缓存一致性协议
三、Unsafe:CAS 的 Java 入口
java
public final class Unsafe {
// CAS 操作,直接调用本地方法
@HotSpotIntrinsicCandidate
public final native boolean compareAndSetInt(Object o, long offset,
int expected, int x);
public final native boolean compareAndSetLong(Object o, long offset,
long expected, long x);
public final native boolean compareAndSetObject(Object o, long offset,
Object expected, Object x);
}
AtomicInteger 源码
java
public class AtomicInteger extends Number implements java.io.Serializable {
private static final Unsafe U = Unsafe.getUnsafe();
private static final long VALUE;
static {
try {
// 获取 value 字段在对象内存中的偏移量
VALUE = U.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));
} catch (ReflectiveOperationException e) {
throw new ExceptionInInitializerError(e);
}
}
private volatile int value; // volatile 保证可见性
public final int incrementAndGet() {
return U.getAndAddInt(this, VALUE, 1) + 1;
}
// Unsafe.getAndAddInt 源码
// JDK 9+ 版本:
@HotSpotIntrinsicCandidate
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
// 自旋 CAS
do {
v = getIntVolatile(o, offset); // 读取当前值
} while (!weakCompareAndSetInt(o, offset, v, v + delta)); // CAS
return v;
}
}
CAS 自旋模式
java
// 通用 CAS 自旋模板
public boolean casUpdate(AtomicInteger ai, int expected, int newVal) {
while (true) {
int current = ai.get(); // 1. 读当前值
if (current != expected) return false; // 2. 期望不匹配
if (ai.compareAndSet(current, newVal)) // 3. CAS
return true; // 4. 成功
// CAS 失败 → 自旋重试
}
}
objectivec
CAS 自旋循环:
┌──────────────────────────────────────────┐
│ while (true) { │
│ V = read() ← 读当前值 │
│ if (CAS(V, V+1)) ← 原子比较交换 │
│ break; ← 成功退出 │
│ // 失败 → 继续循环 │
│ } │
│ │
│ 注意: 无阻塞、无锁、用户态 │
│ 代价: 高竞争下自旋浪费 CPU │
└──────────────────────────────────────────┘
四、ABA 问题
什么是 ABA?
less
初始值: A
线程1: 读到 A
线程2: A → B → A (改了又改回来)
线程1: CAS(A, C) 成功! ← 但中间变化它不知道
问题: 线程1以为值没变过,实际上被改过两次
ABA 的危害示例
java
// 栈的并发弹出(ABA 导致数据丢失)
class ConcurrentStack<T> {
private AtomicReference<Node<T>> top = new AtomicReference<>();
public void push(T item) {
Node<T> newTop = new Node<>(item);
Node<T> oldTop;
do {
oldTop = top.get();
newTop.next = oldTop;
} while (!top.compareAndSet(oldTop, newTop));
}
public T pop() {
Node<T> oldTop;
Node<T> newTop;
do {
oldTop = top.get();
if (oldTop == null) return null;
newTop = oldTop.next;
// ── 线程1 在这里暂停 ──
// 线程2: pop() → top=A→B
// 线程2: pop() → top=B→C
// 线程2: push(A) → top=C→A (A被复用!
// 但 A.next 现在可能指向已回收的 B)
// 线程1 恢复: CAS(A, A.next=B)
// 但 B 已经不在栈中了! 数据损坏
} while (!top.compareAndSet(oldTop, newTop));
return oldTop.item;
}
}
解决方案1:AtomicStampedReference
java
public class ABASolution {
// 加版本号: (值, 版本) 双重校验
private AtomicStampedReference<Integer> ref =
new AtomicStampedReference<>(100, 0); // 初始: 值100, 版本0
public void transfer() {
int[] stampHolder = new int[1];
Integer currentVal = ref.get(stampHolder);
int currentStamp = stampHolder[0];
// CAS 同时比较值和版本号
// A→B→A 后版本号已变,CAS 会失败
ref.compareAndSet(currentVal, 200, currentStamp, currentStamp + 1);
}
}
objectivec
AtomicStampedReference:
┌──────────────────────────────────────────┐
│ (value, stamp) 二元组 │
│ │
│ 初始: (A, 0) │
│ 线程2改: (B, 1) → (A, 2) │
│ 线程1 CAS(A,0, C,3): │
│ 当前 (A, 2) ≠ 期望 (A, 0) │
│ → CAS 失败! 正确检测到 ABA │
└──────────────────────────────────────────┘
解决方案2:AtomicMarkableReference
java
// 只有一个 boolean 标记(不需要递增版本号时用)
AtomicMarkableReference<Node<T>> ref =
new AtomicMarkableReference<>(null, false);
// 适合 "是否被修改过" 的场景,不需要详细版本
ref.compareAndSet(old, newVal, false, true);
五、JDK 原子类全景
基本类型
java
AtomicInteger ai = new AtomicInteger(0);
ai.compareAndSet(0, 1); // CAS
ai.getAndIncrement(); // CAS 自旋
ai.getAndAdd(5); // CAS 自旋累加
AtomicLong al = new AtomicLong(0);
AtomicBoolean ab = new AtomicBoolean(false);
引用类型
java
AtomicReference<User> ref = new AtomicReference<>(new User("Alice"));
ref.updateAndGet(u -> new User("Bob")); // CAS 更新引用
AtomicStampedReference<User> stamped = // 带版本号
new AtomicStampedReference<>(new User("Alice"), 0);
AtomicMarkableReference<User> marked = // 带标记位
new AtomicMarkableReference<>(new User("Alice"), false);
数组类型
java
AtomicIntegerArray arr = new AtomicIntegerArray(10);
arr.compareAndSet(0, 0, 42); // CAS 更新数组元素
AtomicLongArray arrL = new AtomicLongArray(10);
AtomicReferenceArray<String> arrR = new AtomicReferenceArray<>(10);
字段更新器
java
class Counter {
volatile int count; // 必须是 volatile!
}
AtomicIntegerFieldUpdater<Counter> updater =
AtomicIntegerFieldUpdater.newUpdater(Counter.class, "count");
Counter counter = new Counter();
updater.incrementAndGet(counter); // CAS 更新指定对象的字段
高性能累加器
java
// LongAdder: 高并发计数器,分段CAS
LongAdder adder = new LongAdder();
adder.increment(); // 分散到不同 Cell,减少竞争
adder.sum(); // base + Σ Cell.value
// LongAccumulator: 自定义累加函数
LongAccumulator acc = new LongAccumulator(Long::max, Long.MIN_VALUE);
acc.accumulate(42);
acc.get(); // 返回所有 accumulate 的 max
六、LongAdder:CAS 竞争优化
AtomicLong 在高并发下所有线程 CAS 同一个 value 字段,竞争激烈。LongAdder 采用分段 CAS:
objectivec
AtomicLong: 所有线程争抢一个 value
Thread1 CAS(value) ─┐
Thread2 CAS(value) ─┼── 竞争激烈,大量失败自旋
Thread3 CAS(value) ─┘
LongAdder: 分散到 Cell[]
┌──────┐
│ base │ ← 低竞争时直接 CAS
└──────┘
┌───┬───┬───┐
│C0 │C1 │C2 │ ← 高竞争时分散到不同 Cell
│ 3 │ 5 │ 2 │ 每个线程 hash 到不同 Cell,减少竞争
└───┴───┴───┘
sum = base + C0 + C1 + C2 = 10
java
public void add(long x) {
Cell[] as; long b, v; int m; Cell a;
if ((as = cells) != null || !casBase(b = base, b + x)) {
// base CAS 失败 → 尝试 Cell
boolean uncontended = true;
if (as == null || (m = as.length - 1) < 0 ||
(a = as[getProbe() & m]) == null || // 线程哈希定位 Cell
!(uncontended = a.cas(v = a.value, v + x))) // Cell CAS
longAccumulate(x, null, uncontended); // 扩容/初始化
}
}
AtomicLong vs LongAdder 性能对比
java
public class AtomicBenchmark {
static final int THREADS = 100;
static final int LOOPS = 1_000_000;
static void benchAtomicLong() throws Exception {
AtomicLong counter = new AtomicLong();
ExecutorService pool = Executors.newFixedThreadPool(THREADS);
long start = System.nanoTime();
for (int i = 0; i < THREADS; i++)
pool.submit(() -> { for (int j = 0; j < LOOPS; j++) counter.incrementAndGet(); });
pool.shutdown(); pool.awaitTermination(60, TimeUnit.SECONDS);
System.out.printf("AtomicLong: %dms, val=%d%n",
(System.nanoTime() - start) / 1_000_000, counter.get());
}
static void benchLongAdder() throws Exception {
LongAdder counter = new LongAdder();
ExecutorService pool = Executors.newFixedThreadPool(THREADS);
long start = System.nanoTime();
for (int i = 0; i < THREADS; i++)
pool.submit(() -> { for (int j = 0; j < LOOPS; j++) counter.increment(); });
pool.shutdown(); pool.awaitTermination(60, TimeUnit.SECONDS);
System.out.printf("LongAdder: %dms, val=%d%n",
(System.nanoTime() - start) / 1_000_000, counter.sum());
}
public static void main(String[] args) throws Exception {
benchAtomicLong(); // ~2000ms
benchLongAdder(); // ~200ms (10x faster)
}
}
| 维度 | AtomicLong | LongAdder |
|---|---|---|
| 低竞争 | 快 | 略慢(多一次Cell检查) |
| 高竞争 | 慢(CAS频繁失败) | 快(分散竞争) |
| 空间 | 1个long | base + Cell\[\] |
| 精确读 | 精确 | sum()非原子快照 |
| 适用场景 | 低并发精确计数 | 高并发统计 |
七、VarHandle:Java 9+ 的现代 CAS API
Unsafe 的问题
java
// Unsafe 的问题:
// 1. 内部 API (jdk.internal.misc),非公开
// 2. 直接操作内存偏移量,不安全
// 3. JDK 9+ 模块系统限制访问
// 4. 可能被移除
VarHandle 替代方案
java
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
public class VarHandleDemo {
private volatile int count = 0;
// 定义 VarHandle 指向 count 字段
private static final VarHandle COUNT;
static {
try {
COUNT = MethodHandles.lookup()
.findVarHandle(VarHandleDemo.class, "count", int.class);
} catch (Exception e) {
throw new ExceptionInInitializerError(e);
}
}
public int incrementAndGet() {
int oldVal;
do {
oldVal = (int) COUNT.getVolatile(this);
} while (!COUNT.compareAndSet(this, oldVal, oldVal + 1));
return oldVal + 1;
}
// 更简洁的 API
public int getAndAdd(int delta) {
return (int) COUNT.getAndAdd(this, delta); // 底层 CAS 自旋
}
// 原子操作全家桶
public void atomicOps() {
COUNT.setRelease(this, 42); // release 语义写
int v = (int) COUNT.getAcquire(this); // acquire 语义读
COUNT.compareAndSet(this, 42, 100); // CAS
COUNT.getAndSet(this, 200); // 原子交换
COUNT.weakCompareAndSetPlain(this, 200, 300); // 弱 CAS
}
}
VarHandle vs Unsafe 对比
| 维度 | Unsafe | VarHandle |
|---|---|---|
| 模块化 | 内部API,受限 | 公开API |
| 类型安全 | 弱(直接偏移量) | 强(字段绑定) |
| 内存屏障 | 手动(LoadLoad等) | 内置(acquire/release等) |
| 弱CAS | compareAndSwap(弱) | weakCompareAndSet(明确) |
| JIT 优化 | 有(@HotSpotIntrinsic) | 有 |
| 可维护性 | 差(偏移量硬编码) | 好(lookup 绑定) |
| 未来 | 可能废弃 | 官方推荐 |
内存序操作
java
// VarHandle 提供多种内存序语义
// 完全 volatile 语义 (最严格)
COUNT.setVolatile(obj, 42);
int v = (int) COUNT.getVolatile(obj);
COUNT.compareAndSet(obj, 42, 100);
// Release/Acquire 语义 (高效)
COUNT.setRelease(obj, 42); // 写: 前面的写不会被重排到后面
int v = (int) COUNT.getAcquire(obj); // 读: 后面的读不会被重排到前面
COUNT.compareAndSet(obj, 42, 100);
// Opaque 语义 (仅可见性)
COUNT.setOpaque(obj, 42); // 保证可见性,不保证有序性
int v = (int) COUNT.getOpaque(obj);
// Plain 语义 (普通读写)
COUNT.set(obj, 42); // 无任何保证(慎用)
int v = (int) COUNT.get(obj);
八、CAS 的适用与不适用
适用场景
java
// 1. 计数器
AtomicInteger counter;
counter.incrementAndGet();
// 2. 状态机
AtomicReference<State> state;
state.compareAndSet(State.IDLE, State.RUNNING);
// 3. 无锁队列/栈
ConcurrentLinkedQueue<E>; // 基于 CAS
// 4. 并发容器
ConcurrentHashMap<K,V>; // 空桶 CAS 插入
不适用场景
java
// 1. 高竞争计数器 → 用 LongAdder
// AtomicLong CAS 失败率高,自旋浪费 CPU
// 2. 多变量原子更新 → 用锁
// CAS 只能更新一个变量,多变量用 synchronized 或 StampedLock
// 3. 长时间操作 → 用锁
// CAS 自旋 + 长时间操作 = CPU 浪费
九、面试高频问题速答
Q1: CAS 的自旋在什么情况下性能差?
高竞争场景下,大量线程同时 CAS 同一个变量,失败率高,自旋空耗 CPU。解决:LongAdder 分段 CAS,或用锁。
Q2: ABA 问题在实际中常见吗?
实际工程中 ABA 问题不常见,因为大多数业务场景不关心中间变化。但在无锁数据结构(栈、队列)中,ABA 会导致结构损坏,必须用 AtomicStampedReference 解决。
Q3: Unsafe 为什么叫 Unsafe?
它直接操作内存偏移量,可以分配/释放堆外内存、修改任意字段、CAS 任意地址。使用不当会损坏 JVM 内存结构导致崩溃。JDK 9+ 通过模块系统限制其访问。
Q4: VarHandle 会完全替代 Unsafe 吗?
VarHandle 提供了类型安全的公开 API 替代不安全的 CAS 操作。但 Unsafe 还有分配堆外内存、对象字段定位等功能 VarHandle 不覆盖。JDK 9+ 逐步用 MemorySegment(Foreign Function API)替代堆外内存操作。
Q5: weakCompareAndSet 和 compareAndSet 有什么区别?
weakCompareAndSet 允许"虚假失败"------即使值匹配也可能返回 false。它不提供 happens-before 语义保证。优势是在某些硬件上可以用更轻量的指令实现,适合自旋循环中减少开销。JDK 9+ VarHandle 细化为 weakCompareAndSetPlain/Volatile/Acquire/Release。
Q6: AtomicInteger 的 incrementAndGet 是一次性 CAS 吗?
不是,是 CAS 自旋。底层 getAndAddInt 中的 do-while 循环:读当前值→CAS 更新→失败则重试。竞争不激烈时一次成功,激烈时多次重试。
十、总结
objectivec
CAS 核心知识:
┌──────────────────────────────────────────────────────┐
│ │
│ 原理: Compare And Swap (硬件 cmpxchg 指令) │
│ Java入口: Unsafe → VarHandle (Java 9+) │
│ 自旋: 失败重试,用户态无锁 │
│ │
│ ABA问题: │
│ 值A→B→A,CAS无法检测 │
│ 解决: AtomicStampedReference (值+版本号) │
│ AtomicMarkableReference (值+标记位) │
│ │
│ 竞争优化: │
│ AtomicLong → 高竞争 CAS 失败率高 │
│ LongAdder → 分段 CAS (base + Cell[]) │
│ 高并发计数首选 LongAdder │
│ │
│ API演进: │
│ Unsafe.compareAndSwapXxx (JDK 8-) │
│ → VarHandle.compareAndSet (JDK 9+) │
│ → 类型安全 + 模块化 + 多内存序语义 │
│ │
│ 适用: 低中竞争、无锁数据结构 │
│ 不适用: 高竞争(用LongAdder)、多变量(用锁) │
│ │
└──────────────────────────────────────────────────────┘
CAS 是 Java 并发编程的基石------从 AtomicInteger 到 AQS,从 ConcurrentHashMap 到 LinkedBlockingQueue,底层都依赖 CAS。理解 CAS 的原理、ABA 问题、竞争优化和 API 演进,是掌握 Java 并发的必修课。