Chapter 09

原子类与 CAS

无锁编程的核心——CAS 原理、Atomic 系列、LongAdder、VarHandle 与无锁数据结构

CAS:Compare-And-Swap

什么是 CAS?

CAS(Compare-And-Swap,比较并交换)是一种无锁的原子操作, 由 CPU 硬件直接支持(x86 的 CMPXCHG 指令)。 其语义为:

CAS 语义

原子地执行:if (内存地址V的当前值 == 期望值A) { 将V的值更新为新值B; return true; } else { return false; }

整个比较+更新是一个不可分割的原子操作,无需加锁。

CAS 操作流程: 线程 A 执行 CAS(V, expected=5, newValue=6): ┌─────────────────────────────────────────────┐ │ 1. 读取内存 V 的当前值 │ │ 2. 比较:当前值 == expected(5)? │ │ YES → 将 V 更新为 newValue(6),返回 true │ │ NO → 不更新,返回 false │ │ 注意:步骤 1+2+3 是 CPU 级别的原子操作 │ └─────────────────────────────────────────────┘ 若 CAS 失败(被其他线程抢先修改): ┌────────────────────────────────────────────┐ │ 重新读取最新值 → 重新计算 → 再次尝试 CAS │ │ (自旋重试,直到成功) │ └────────────────────────────────────────────┘

CAS vs 锁

CAS(乐观锁)

  • 假设没有冲突,直接操作
  • 冲突时重试(自旋)
  • 无内核态切换
  • 高竞争时自旋浪费 CPU
  • 适合:低竞争、操作短小

synchronized/Lock(悲观锁)

  • 假设有冲突,先加锁
  • 冲突时阻塞(休眠)
  • 涉及内核态切换
  • 高竞争时线程排队等待
  • 适合:高竞争、操作复杂

Atomic 原子类

AtomicInteger / AtomicLong

import java.util.concurrent.atomic.*;

AtomicInteger counter = new AtomicInteger(0);

// 基本操作
counter.get();                        // 读取当前值(volatile 语义)
counter.set(10);                       // 设置值(volatile 写)
counter.getAndIncrement();            // i++(返回旧值)
counter.incrementAndGet();            // ++i(返回新值)
counter.getAndAdd(5);                // i += 5(返回旧值)
counter.addAndGet(5);                // i += 5(返回新值)
counter.compareAndSet(10, 20);       // CAS:若当前为10,则改为20

// Java 9+ 新增
counter.getAndUpdate(v -> v * 2);    // 原子更新,返回旧值
counter.updateAndGet(v -> v * 2);    // 原子更新,返回新值
counter.accumulateAndGet(3, Integer::sum); // 原子 reduce

// 实现线程安全计数器
public class SafeCounter {
    private final AtomicInteger count = new AtomicInteger();
    public void increment() { count.incrementAndGet(); }
    public int  get()       { return count.get(); }
}

AtomicReference:原子引用

// AtomicReference<T> 对对象引用进行原子 CAS 操作
AtomicReference<String> atomicRef = new AtomicReference<>("initial");

// CAS:若当前值为 "initial",则替换为 "updated"
boolean success = atomicRef.compareAndSet("initial", "updated");
System.out.println(success); // true

// 无锁栈(Treiber Stack)实现
public class LockFreeStack<T> {
    private static class Node<T> {
        final T value;
        Node<T> next;
        Node(T value) { this.value = value; }
    }

    private final AtomicReference<Node<T>> top = new AtomicReference<>();

    public void push(T value) {
        Node<T> newNode = new Node<>(value);
        do {
            newNode.next = top.get(); // 读取当前栈顶
        } while (!top.compareAndSet(newNode.next, newNode)); // CAS 设置新栈顶
    }

    public T pop() {
        Node<T> current;
        do {
            current = top.get();
            if (current == null) return null;
        } while (!top.compareAndSet(current, current.next)); // CAS 弹出栈顶
        return current.value;
    }
}

ABA 问题

CAS 只比较值是否相等,但值相同不代表没有变化。假设变量初始为 A, 线程 1 准备做 CAS(A→C),此时线程 2 先把 A 改为 B,再改回 A——线程 1 的 CAS 依然成功, 但实际上数据已经被修改过了。这就是 ABA 问题

// 解决方案:AtomicStampedReference(版本戳)
AtomicStampedReference<String> ref =
    new AtomicStampedReference<>("A", 0); // 初始值,初始戳=0

int[] stamp = new int[1];
String current = ref.get(stamp); // 同时获取值和戳

// CAS:同时比较值和戳,两者都匹配才更新
boolean ok = ref.compareAndSet(
    "A", "C",  // 期望值→新值
    stamp[0], stamp[0] + 1  // 期望戳→新戳(每次操作递增)
);
// 即使值从 A→B→A,但戳是唯一递增的,不会误判

// AtomicMarkableReference:用 boolean 标记(简化版)
AtomicMarkableReference<Node> nodeRef =
    new AtomicMarkableReference<>(node, false);
nodeRef.compareAndSet(node, newNode, false, true); // 同时更新值和标记

LongAdder:高性能累加器

AtomicLong 在高并发下(大量线程同时 CAS)会产生大量竞争,导致频繁自旋重试,性能下降。 LongAdder(Java 8 引入)通过分散热点解决这一问题: 内部维护一个 base 变量和多个 Cell(每个 Cell 是一个 long), 每个线程优先更新自己的 Cell,读取时汇总所有 Cell + base。

AtomicLong:所有线程竞争同一个变量 Thread-1 ──► CAS(counter) ← 竞争! Thread-2 ──► CAS(counter) ← 重试! Thread-3 ──► CAS(counter) ← 自旋! Thread-4 ──► CAS(counter) LongAdder:线程分散到不同 Cell,减少竞争 Thread-1 ──► Cell[0] += 1 ← 互不干扰 Thread-2 ──► Cell[1] += 1 Thread-3 ──► Cell[2] += 1 Thread-4 ──► Cell[3] += 1 sum() = base + Cell[0] + Cell[1] + Cell[2] + Cell[3]
LongAdder adder = new LongAdder();

adder.increment();              // +1
adder.decrement();              // -1
adder.add(10);                  // += 10
long total = adder.sum();        // 获取总和(近似值,并发时可能略有偏差)
long exact = adder.sumThenReset(); // 获取并重置(常用于统计)

// 性能对比:高并发计数器
// AtomicLong:竞争激烈时 CAS 不断失败重试,QPS 约 50M/s(8核)
// LongAdder:分散写,QPS 约 400M/s(8核),性能提升8倍+

// 同类:LongAccumulator(支持自定义累积函数)
LongAccumulator maxVal = new LongAccumulator(Long::max, Long.MIN_VALUE);
maxVal.accumulate(42);  // 原子 max 操作
maxVal.accumulate(100);
System.out.println(maxVal.get()); // 100

Atomic 数组类

// AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray
AtomicIntegerArray arr = new AtomicIntegerArray(10); // 长度10的原子整型数组

arr.set(0, 100);                // 设置 arr[0] = 100
arr.getAndIncrement(0);         // arr[0]++
arr.compareAndSet(0, 101, 200);  // CAS arr[0]: 101 → 200

// 使用场景:并行更新数组的不同位置(无需整体加锁)
AtomicIntegerArray histogram = new AtomicIntegerArray(256);
// 多线程并行统计字节频率
for (byte b : data) {
    histogram.incrementAndGet(b & 0xFF);
}

Atomic 字段更新器

// 对已有类的 volatile 字段进行原子操作,无需修改原类
public class Node {
    volatile int value; // 必须是 volatile!
}

// 创建更新器(一次性对象,可复用)
AtomicIntegerFieldUpdater<Node> updater =
    AtomicIntegerFieldUpdater.newUpdater(Node.class, "value");

Node node = new Node();
updater.set(node, 10);
updater.compareAndSet(node, 10, 20);
System.out.println(node.value); // 20

VarHandle(Java 9+)

VarHandle(JEP 193,Java 9)是更底层、更通用的变量访问句柄, 可以对变量(字段、数组元素、静态变量)执行不同内存语义的读写操作, 替代了 Unsafe 和字段更新器的大部分使用场景。

getVolatile / setVolatile
带 volatile 语义的读写,等效于对 volatile 变量的读写,提供完整的内存可见性和有序性保证。
getOpaque / setOpaque
不提供跨线程可见性保证,但保证单线程内的顺序性。比 plain 语义强,比 volatile 弱。适用于线程本地或只需要单线程有序的场景。
getAcquire / setRelease
Acquire/Release 语义(C++ memory_order_acquire/release)。Acquire 读防止后续读写重排到前面;Release 写防止前面读写重排到后面。比 volatile 稍弱,性能更好。
compareAndSet / compareAndExchange
CAS 操作,compareAndSet 返回 boolean,compareAndExchange 返回实际观测到的旧值(失败时)。
import java.lang.invoke.*;

class Counter {
    private volatile int count = 0;

    // 获取 count 字段的 VarHandle
    private static final VarHandle COUNT;
    static {
        try {
            COUNT = MethodHandles.lookup()
                .findVarHandle(Counter.class, "count", int.class);
        } catch (Exception e) { throw new ExceptionInInitializerError(e); }
    }

    public int incrementAndGet() {
        int v;
        do {
            v = (int) COUNT.getVolatile(this);
        } while (!COUNT.compareAndSet(this, v, v + 1));
        return v + 1;
    }

    // 使用 getAndAdd(VarHandle 内置的原子操作)
    public int getAndAdd(int delta) {
        return (int) COUNT.getAndAdd(this, delta);
    }
}

原子类选型指南

场景 推荐 说明
单个 int/long 的线程安全操作 AtomicInteger / AtomicLong 通用,API 丰富
高并发累加计数器(只写,定期读) LongAdder 写分散,高并发性能远优于 AtomicLong
高并发求最大/最小值 LongAccumulator 支持自定义累积函数
原子替换对象引用 AtomicReference 无锁状态切换
需要版本号防 ABA AtomicStampedReference 值+戳双重 CAS
已有类的 volatile 字段原子操作 VarHandle(Java 9+) 取代字段更新器和 Unsafe
数组元素的原子操作 AtomicIntegerArray 等 每个元素独立原子操作

本章小结