跳到主要内容

CAS 与原子类

问题

什么是 CAS?CAS 有什么问题?Java 中的原子类有哪些?LongAdder 为什么比 AtomicLong 性能好?

答案

什么是 CAS?

CAS(Compare And Swap/Set) 是一种无锁的原子操作,也叫乐观锁。核心思想:

修改前先比较当前值是否与预期值相同,相同则更新为新值,否则重试。

CAS 伪代码
// 三个参数:内存地址 V、期望值 A、新值 B
boolean CAS(V, A, B) {
if (V 的当前值 == A) {
V = B; // 更新成功
return true;
}
return false; // 更新失败,需重试
}

CAS 是一条 CPU 原子指令(如 x86 的 CMPXCHG),由硬件保证原子性,不需要加锁。

Java 中的 CAS:Unsafe 类

Java 通过 sun.misc.Unsafe 类提供 CAS 操作(JDK 9+ 为 jdk.internal.misc.Unsafe):

Unsafe CAS 方法
// 对象 o 的偏移量 offset 处的值,如果等于 expected,则更新为 x
public final native boolean compareAndSwapInt(Object o, long offset, int expected, int x);
public final native boolean compareAndSwapLong(Object o, long offset, long expected, long x);
public final native boolean compareAndSwapObject(Object o, long offset, Object expected, Object x);

JDK 9+ 引入了 VarHandle,提供更安全的替代方案。

原子类(java.util.concurrent.atomic)

基本类型原子类

AtomicInteger 使用
AtomicInteger count = new AtomicInteger(0);

count.get(); // 获取当前值
count.set(10); // 设置值
count.incrementAndGet(); // ++i,返回新值
count.getAndIncrement(); // i++,返回旧值
count.addAndGet(5); // 加 5,返回新值
count.compareAndSet(15, 20); // CAS:期望 15 则更新为 20

// 自定义更新函数(JDK 8+)
count.updateAndGet(x -> x * 2); // 自定义运算
count.accumulateAndGet(10, Integer::sum); // 累加

AtomicInteger 核心源码

AtomicInteger.incrementAndGet(简化)
public class AtomicInteger {
// 使用 volatile 保证可见性
private volatile int value;
// 值在对象中的内存偏移量
private static final long valueOffset;

public final int incrementAndGet() {
// 自旋 CAS
for (;;) {
int current = get(); // 读取当前值
int next = current + 1; // 计算新值
if (compareAndSet(current, next)) // CAS 尝试更新
return next; // 成功返回
// 失败则自旋重试
}
}

// 底层调用 Unsafe
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
}

原子类家族一览

分类说明
基本类型AtomicInteger / AtomicLong / AtomicBoolean基本类型原子操作
引用类型AtomicReference<V>对象引用原子操作
数组类型AtomicIntegerArray / AtomicLongArray / AtomicReferenceArray数组元素原子操作
字段更新器AtomicIntegerFieldUpdater / AtomicLongFieldUpdater / AtomicReferenceFieldUpdater已有对象 volatile 字段原子更新
带版本号AtomicStampedReference / AtomicMarkableReference解决 ABA 问题
累加器LongAdder / LongAccumulator / DoubleAdder / DoubleAccumulator高性能计数器

CAS 的三大问题

1. ABA 问题

线程 1 读到值 A,线程 2 将 A 改为 B 再改回 A,线程 1 CAS 检查仍为 A 并更新成功,但值已经被改过了。

解决方案:版本号

AtomicStampedReference 解决 ABA
// 初始值 "A",初始版本号 1
AtomicStampedReference<String> ref = new AtomicStampedReference<>("A", 1);

// 获取当前值和版本号
String value = ref.getReference();
int stamp = ref.getStamp();

// CAS 时同时比较值和版本号
boolean success = ref.compareAndSet(
"A", // 期望值
"C", // 新值
stamp, // 期望版本号
stamp + 1 // 新版本号
);

AtomicMarkableReference 只关心是否被修改过(boolean 标记),不关心修改次数。

2. 自旋开销

高竞争下 CAS 频繁失败,自旋消耗 CPU。

解决方案

  • 使用 LongAdder 分散竞争
  • 设置自旋次数上限,超过后阻塞
  • 使用 JDK 的适应性自旋

3. 只能保证一个共享变量的原子操作

CAS 操作的是单个变量。如果需要原子更新多个变量:

  • 将多个变量合并到一个对象中,使用 AtomicReference
  • 使用锁

LongAdder 高性能计数器

LongAdder 是 JDK 8 引入的,在高竞争下性能远超 AtomicLong

核心思想:分散热点

  • AtomicLong:所有线程竞争同一个 value,高并发下 CAS 频繁失败
  • LongAdder:维护一个 base + Cell[] 数组,每个线程 CAS 自己对应的 Cell,最终 sum = base + ΣCell[i]
LongAdder 使用
LongAdder adder = new LongAdder();

// 多线程累加
adder.increment(); // +1
adder.add(10); // +10

// 获取总和(注意:非精确值,因为可能正在被修改)
long sum = adder.sum(); // base + Cell[0] + Cell[1] + ...
adder.reset(); // 重置为 0
adder.sumThenReset(); // 获取总和并重置

LongAdder vs AtomicLong

对比AtomicLongLongAdder
实现单 volatile + CASbase + Cell[] 分散
低竞争性能好(只用 base)
高竞争性能差(CAS 频繁失败)(分散竞争)
精确读取精确非精确(sum 不保证原子性)
适用场景需要精确值的场景统计计数器、metrics
LongAccumulator

LongAccumulatorLongAdder 的通用版本,支持自定义累加函数:

// 相当于 LongAdder
LongAccumulator adder = new LongAccumulator(Long::sum, 0);
// 最大值
LongAccumulator max = new LongAccumulator(Long::max, Long.MIN_VALUE);

常见面试问题

Q1: CAS 的原理是什么?

答案

CAS(Compare And Swap)是一种无锁的原子操作。包含三个操作数:内存值 V、预期值 A、新值 B。当且仅当 V == A 时,将 V 更新为 B,否则不做操作。整个比较+更新操作由 CPU 原子指令保证(x86 的 CMPXCHG),不需要加锁。

Java 通过 Unsafe.compareAndSwapXxx() 方法调用 native CAS 指令。

Q2: ABA 问题如何解决?

答案

使用 AtomicStampedReference,在 CAS 的同时比较版本号。每次更新时版本号 +1,即使值从 A → B → A,版本号也会变化(1 → 2 → 3),CAS 会检测到变化而失败。

如果只关心"是否被修改过"而不关心修改次数,可以用 AtomicMarkableReference(boolean 标记)。

Q3: CAS 和 synchronized 的区别?

答案

对比CAS(乐观锁)synchronized(悲观锁)
加锁方式无锁,自旋重试获取锁,阻塞等待
线程阻塞不阻塞可能阻塞
适用场景竞争不激烈、操作轻量竞争激烈、操作复杂
CPU 消耗高竞争下自旋浪费 CPU阻塞后不消耗 CPU
死锁不会可能

Q4: LongAdder 的原理?为什么比 AtomicLong 快?

答案

LongAdder 使用分段思想,维护一个 base 和一个 Cell[] 数组:

  1. 低竞争:CAS 更新 base
  2. CAS base 失败:将线程映射到 Cell 数组的某个槽位,CAS 更新该 Cell
  3. 求和:sum = base + ΣCell[i]

比 AtomicLong 快的原因:将单一热点分散到多个 Cell,减少了 CAS 竞争失败的概率。代价是 sum() 不保证精确性(读取过程中可能有修改)。

Q5: AtomicInteger 是如何保证线程安全的?

答案

两个关键保障:

  1. volatilevalue 字段用 volatile 修饰,保证可见性和有序性
  2. CAS:所有修改操作通过 Unsafe.compareAndSwapInt() 实现原子更新

volatile + CAS = 无锁线程安全。

Q6: CAS 在 JDK 中有哪些应用?

答案

  • 原子类:AtomicInteger、AtomicLong、AtomicReference 等
  • AQS:state 的 CAS 更新(ReentrantLock、Semaphore 等)
  • ConcurrentHashMap:节点插入、size 计算
  • synchronized 锁升级:偏向锁 CAS 设置线程 ID、轻量级锁 CAS 设置 Lock Record

相关链接