CAS 与原子类
问题
什么是 CAS?CAS 有什么问题?Java 中的原子类有哪些?LongAdder 为什么比 AtomicLong 性能好?
答案
什么是 CAS?
CAS(Compare And Swap/Set) 是一种无锁的原子操作,也叫乐观锁。核心思想:
修改前先比较当前值是否与预期值相同,相同则更新为新值,否则重试。
// 三个参数:内存地址 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):
// 对象 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 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 核心源码
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 并更新成功,但值已经被改过了。
解决方案:版本号
// 初始值 "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 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
| 对比 | AtomicLong | LongAdder |
|---|---|---|
| 实现 | 单 volatile + CAS | base + Cell[] 分散 |
| 低竞争性能 | 好 | 好(只用 base) |
| 高竞争性能 | 差(CAS 频繁失败) | 好(分散竞争) |
| 精确读取 | 精确 | 非精确(sum 不保证原子性) |
| 适用场景 | 需要精确值的场景 | 统计计数器、metrics |
LongAccumulator 是 LongAdder 的通用版本,支持自定义累加函数:
// 相当于 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[] 数组:
- 低竞争:CAS 更新
base - CAS base 失败:将线程映射到 Cell 数组的某个槽位,CAS 更新该 Cell
- 求和:
sum = base + ΣCell[i]
比 AtomicLong 快的原因:将单一热点分散到多个 Cell,减少了 CAS 竞争失败的概率。代价是 sum() 不保证精确性(读取过程中可能有修改)。
Q5: AtomicInteger 是如何保证线程安全的?
答案:
两个关键保障:
- volatile:
value字段用 volatile 修饰,保证可见性和有序性 - CAS:所有修改操作通过
Unsafe.compareAndSwapInt()实现原子更新
volatile + CAS = 无锁线程安全。
Q6: CAS 在 JDK 中有哪些应用?
答案:
- 原子类:AtomicInteger、AtomicLong、AtomicReference 等
- AQS:state 的 CAS 更新(ReentrantLock、Semaphore 等)
- ConcurrentHashMap:节点插入、size 计算
- synchronized 锁升级:偏向锁 CAS 设置线程 ID、轻量级锁 CAS 设置 Lock Record
相关链接
- java.util.concurrent.atomic - Java 17 API
- LongAdder - Java 17 API
- synchronized 关键字 - CAS 在锁升级中的应用
- Lock 接口与 AQS - CAS 在 AQS 中的应用
- volatile 关键字 - volatile 可见性保证
- ConcurrentHashMap - CAS 在并发集合中的应用