← 返回 JUC 列表

原子类与 CAS

原子类与 CAS

一、CAS(Compare And Swap)

知识点说明

CAS 是无锁并发的基础,也叫乐观锁。它的核心思想:先比较,再交换,全部在一个原子操作中完成。

java
// CAS 的伪代码
boolean compareAndSwap(int[] arr, int offset, int expectedValue, int newValue) {
    if (arr[offset] == expectedValue) {
        arr[offset] = newValue;
        return true;  // 更新成功
    }
    return false;     // 更新失败(值被其他线程改了)
}

CAS 由 CPU 硬件提供原子性支持(cmpxchg 指令),不需要加锁,所以称为无锁编程

CAS 的三个操作数

内存偏移地址 V(要修改的变量的地址)
预期值 A(当前线程认为变量当前值是多少)
新值 B(要修改成什么值)

算法:如果 V 的实际值 == A,则把 V 改为 B,否则不做任何事

Java 中的 CAS 实现

底层通过 Unsafe 类调用 JNI 方法:

java
Unsafe 类提供的 CAS 方法:
boolean compareAndSwapObject(Object o, long offset, Object expected, Object x)
boolean compareAndSwapInt(Object o, long offset, int expected, int x)
boolean compareAndSwapLong(Object o, long offset, long expected, long x)

// offset = 变量在对象中的内存偏移地址(通过 Unsafe.objectFieldOffset() 获取)

CAS 的优缺点

优点:

  • 没有锁竞争的开销(不需要上下文切换)
  • 性能高(特别是竞争不激烈时)

缺点:

  • ABA 问题(下文详述)
  • 自旋(循环重试)消耗 CPU
  • 只能保证单个变量的原子性

二、ABA 问题

什么是 ABA?

线程 1 读到 A
线程 2 把 A → B → A(改回去)
线程 1 再次 CAS 比较 → 值还是 A → CAS 成功(但其实值已经被改过了)

ABA 的危害

java
// ABA 不影响直接赋值场景(反正值又变回来了)
// 但某些场景有危害:
// 场景:链表操作

// 初始:Head → NodeA → NodeB
// 线程 1:准备 pop NodeA(期望 Head.next == NodeA)
// 线程 2:pop NodeA(Head → NodeB)
// 线程 2:push NodeA 回来(Head → NodeA → NodeD)
// 线程 1:CAS 发现 Head.next 还是 NodeA,认为没变,把 Head 设为 NodeB
// 结果:NodeD 丢失了!

解决方案:AtomicStampedReference

使用版本号(或时间戳)来识别 ABA:

java
// AtomicStampedReference:带版本号的原子引用
AtomicStampedReference<String> ref = new AtomicStampedReference<>("A", 0);

int[] stampHolder = new int[1];
String value = ref.get(stampHolder); // value = "A", stampHolder[0] = 0

// 更新时检查引用和版本号
boolean success = ref.compareAndSet(
    "A",           // 期望引用
    "B",           // 新引用
    0,             // 期望版本号
    1              // 新版本号
);

// 也可以使用 AtomicMarkableReference(布尔标记版)
AtomicMarkableReference<String> markRef = new AtomicMarkableReference<>("A", false);

CAS 自旋的代价

java
// 高并发下 CAS 可能失败 → 循环重试(自旋)
// 冲突严重时,自旋次数暴增,CPU 飙升

// 示例:AtomicInteger 的 incrementAndGet
// public final int incrementAndGet() {
//     for (;;) {                    // 自旋
//         int current = get();
//         int next = current + 1;
//         if (compareAndSet(current, next))  // CAS 尝试
//             return next;           // 成功返回
//     }                              // 失败重试
// }

当冲突激烈时,CAS 性能不如锁——因为大量 CPU 时间消耗在自旋上。


三、原子类(Atomic 系列)

种类一览

类别 类名 说明
基本类型 AtomicInteger 原子整型
基本类型 AtomicLong 原子长整型
基本类型 AtomicBoolean 原子布尔型
引用类型 AtomicReference 原子对象引用
引用类型 AtomicStampedReference 带版本号的引用(解决 ABA)
引用类型 AtomicMarkableReference 带布尔标记的引用
数组 AtomicIntegerArray 原子整型数组元素
数组 AtomicLongArray 原子长整型数组元素
数组 AtomicReferenceArray 原子引用数组元素
累加器 LongAdder(JDK 8) 高性能原子累加器
累加器 LongAccumulator 支持自定义操作的累加器

AtomicInteger 示例

java
AtomicInteger count = new AtomicInteger(0);

// 常用的方法
count.get();                    // 获取当前值
count.set(10);                  // 设置值
count.getAndSet(20);            // 获取旧值并设置新值

count.incrementAndGet();        // ++i(先加后取)
count.getAndIncrement();        // i++(先取后加)
count.addAndGet(5);             // += 5 并返回

count.compareAndSet(expect, update); // CAS 操作

// 实际应用:计数器
public class RequestCounter {
    private final AtomicLong totalRequests = new AtomicLong(0);
    private final AtomicLong successRequests = new AtomicLong(0);

    public void recordRequest(boolean success) {
        totalRequests.incrementAndGet();
        if (success) {
            successRequests.incrementAndGet();
        }
    }

    public double successRate() {
        long total = totalRequests.get();
        if (total == 0) return 0;
        return (double) successRequests.get() / total;
    }
}

AtomicReference 示例

java
// 原子更新对象引用
AtomicReference<String> ref = new AtomicReference<>("初始值");
ref.compareAndSet("初始值", "新值");

// 原子更新对象
AtomicReference<BigDecimal> balance = new AtomicReference<>(BigDecimal.ZERO);

public void deposit(BigDecimal amount) {
    while (true) {
        BigDecimal current = balance.get();
        BigDecimal updated = current.add(amount);
        if (balance.compareAndSet(current, updated)) {
            break;
        }
    }
}

四、LongAdder(JDK 8 高性能原子类)

为什么要 LongAdder?

AtomicLong 在高并发下 CAS 竞争激烈,大量线程自旋浪费 CPU。

LongAdder 将单一 value 拆分为多个 cell:

  • 竞争不激烈时:直接 CAS 更新 base
  • 竞争激烈时:分散到 Cell[] 数组,每个线程更新自己的 cell
  • sum() 时累加 base + Cell[] 所有值
AtomicLong 模式:           LongAdder 模式:
┌──────────────┐           ┌──────────────┐
│   value      │           │   base       │  ← 低竞争时使用
│   (一个点)    │           ├──────────────┤
└──────────────┘           │  Cell[0]     │  ← 线程 A 用
▲                           │  Cell[1]     │  ← 线程 B 用
│CAS 全挤在这里              │  Cell[2]     │  ← 线程 C 用
多线程竞争                    │    ...       │
                           └──────────────┘

示例代码

java
// LongAdder vs AtomicLong 对比
LongAdder adder = new LongAdder();
AtomicLong atomicLong = new AtomicLong(0);

// 多线程累加
// 高并发下 LongAdder 性能远超 AtomicLong
adder.increment();  // +1
adder.add(10);      // +10
long sum = adder.sum();       // 获取当前总和
long sumThenReset = adder.sumThenReset(); // 获取并重置

// 适用:统计类场景(QPS 统计、请求计数、耗时统计)
// 不适用:需要精确全局序数的场景(如 ID 生成器)

// LongAccumulator — 支持自定义累加函数
LongAccumulator accumulator = new LongAccumulator(Long::max, Long.MIN_VALUE);
accumulator.accumulate(100);  // max(初始值, 100) = 100
accumulator.accumulate(50);   // max(100, 50) = 100
accumulator.accumulate(200);  // max(100, 200) = 200
System.out.println(accumulator.get()); // 200

AtomicLong vs LongAdder 选择

对比 AtomicLong LongAdder
原理 CAS + 自旋 分散 cell,空间换时间
低并发 ✅ 性能好 ✅ 性能好
高并发 ❌ CAS 自旋飙升 ✅ Cell 分散竞争
空间 一个变量 base + Cell[n] 数组
适用 需要精确值(如 ID 生成) 统计计数类(如 QPS、总量)

注意事项

  1. LongAdder 不保证实时一致性——sum() 遍历 cell 时可能被其他线程修改
  2. sum() 是累加不是快照——多线程同时 sum() 可能得到不同结果
  3. LongAdder 适合统计计数,不适合需要精确值作为唯一序列的场景
  4. AtomicInteger/AtomicLong 的 incrementAndGet() 有返回值,LongAdder 没有(增后值需要通过 sum() 获得,但不精确)
  5. 高并发下优先用 LongAdder 替代 AtomicLong 作为计数器

五、Unsafe 类(了解即可)

知识点说明

Unsafe 类提供直接操作内存的能力,是 JUC 所有 CAS 操作的底层支撑。

java
// Unsafe 无法直接 new,需要通过反射获取
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
Unsafe unsafe = (Unsafe) f.get(null);

核心方法

java
// 内存操作
long allocateMemory(long bytes);    // 分配堆外内存
void freeMemory(long address);      // 释放堆外内存
void putLong(long address, long x); // 写入指定地址

// CAS 操作
boolean compareAndSwapInt(Object o, long offset, int expected, int x);

// 偏移量获取
long objectFieldOffset(Field field); // 获取对象字段的偏移量

// 线程操作
void park(boolean isAbsolute, long time);
void unpark(Object thread);

使用注意

  1. 不是标准 API——不同 JDK 版本可能变化
  2. 有安全限制——Java 9+ 模块化后限制更严格
  3. 不建议直接使用——优先使用 Atomic 系列和并发工具

六、CAS 面试常见问答

Q1:CAS 和锁的对比?

CAS(乐观锁) 锁(悲观锁)
思想 假设没冲突,先改再说 假设一定有冲突,先锁上
阻塞 不阻塞(自旋) 阻塞/唤醒
CPU 消耗 冲突时自旋消耗 CPU 线程切换消耗
适用 冲突少的场景 冲突多的场景

Q2:CAS 的三大问题?

  1. ABA 问题 → 版本号解决(AtomicStampedReference)
  2. 自旋消耗 CPU → 冲突严重时不如锁
  3. 只能保证一个变量的原子性 → 多个变量需用锁或 AtomicReference 封装

Q3:LongAdder 为什么比 AtomicLong 快?

空间换时间:每个线程更新自己的 Cell,避免多线程同时对一个变量 CAS 竞争。