CAS 到底是怎么保证原子性的?从 Unsafe 到 VarHandle 的演进

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 并发的必修课。

相关推荐
一条小小yu8 分钟前
Spring IoC的理解
java·后端·spring
落魄实习生36 分钟前
Agent Scope Java 2.x 系列【7】工具使用
java·开发语言·ai
旺仔学长 哈哈42 分钟前
springboot钓鱼爱好者交流平台APP设计与实现
java·spring boot·mysql·充电桩管理系统
自强的小白2 小时前
核心功能(Service接口)
java·mybatis
乌暮3 小时前
深入理解 Java 泛型:把「万能盒子」用对、用稳
java·开发语言·后端·学习
大猫和小黄3 小时前
深入理解 Java Optional:从设计哲学到工程实战
java
数据库百宝箱4 小时前
rum&gin索引对比
java·数据库·gin
yychen_java4 小时前
第六篇:Spring AI 实战:将 Java 业务接口封装成企业级 MCP Server
java·人工智能·spring
_upupup4 小时前
异常(C++)
java·开发语言·jvm