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

相关推荐
Zane19941 小时前
线上突然OOM,你的排查顺序是先看日志还是先重启
java·后端
君顾12 小时前
折扣卡 CPS 小程序定制:从业务流程到系统架构的实践指南
java·开发语言·折扣卡
见不散2 小时前
GPT-5.6 Luna (Batch) 批量处理能力深度评测
java·gpt·batch
计算机毕设定制辅导-无忧学长2 小时前
《基于SpringBoot的庭院玫瑰栽培养护知识交互式科普平台设计与实现》
java·spring boot·后端·毕业设计·个性化推荐·协同过滤算法·庭院玫瑰栽培养护知识科普平台
Json____3 小时前
从零构建家政服务平台:一套全栈架构如何打通管理端与移动端-java-springboot
java·spring boot·后端·架构·毕设·wwwoop.com
云雀衔光3 小时前
Java Spring AI MCP:在 Spring 里把 AI 接上你的业务系统
java·开发语言·人工智能·后端·测试工具·spring
重生之小比特3 小时前
【Java SE】数据类型与变量
java·开发语言·python
苏渡苇3 小时前
Spring Insight 里如何对 Span 进行清洗
java·spring boot·后端·spring·系统监控
find1star3 小时前
LeetCode 54:螺旋矩阵——用四个边界模拟矩阵收缩
java·算法·leetcode·边缘计算·学习方法