引言
在高并发编程领域,线程安全是永恒的核心议题。传统的悲观锁机制(如 synchronized、ReentrantLock)通过阻塞线程实现资源互斥,虽能保证安全性,但会带来上下文切换、线程唤醒等性能开销。而 CAS(Compare-And-Swap,比较并交换)作为乐观锁的核心实现,以无锁方式实现原子操作,成为高并发场景下提升性能的关键技术。
一、什么是 CAS
- CAS(Compare-And-Swap):即比较并交换,是一种无锁的并发控制技术
- 包含三个操作数
- 内存位置(V):需要更新的共享变量在内存中的地址
- 预期原值(A):线程读取到的变量当前值,即期望变量保持的值
- 目标新值(B):变量需要更新到的最终值
CAS 的操作流程可概括为:读取内存位置 V 的当前值,若该值与预期原值 A 一致,则将 V 更新为新值 B 并返回成功;若不一致,则说明变量已被其他线程修改,放弃本次更新并返回失败。
boolean compareAndSwap(MemoryAddress V, ExpectedValue A, NewValue B) {
if (V == A) {
V = B;
return true;
}
return false;
}
其核心思想是'乐观假设无冲突'——默认不会有其他线程同时修改变量,仅在冲突发生时通过重试(而非阻塞)规避问题,从而避免了锁机制的性能损耗。
二、CAS 的底层实现
CAS 的原子性无法通过纯软件实现,必须依赖硬件层面的原子指令支持,整个依赖链路分为三层。
1、CPU 原子指令支撑
- 不同架构的 CPU 提供了专门的原子指令来实现 CAS 操作:
- x86 架构:提供
cmpxchg(Compare and Exchange)指令,该指令可在一条指令内完成'比较 - 交换'的原子操作
- ARM 架构:通过
ldrex(加载并标记)和 strex(存储并检查标记)指令对实现,标记期间其他线程无法修改目标内存地址,确保操作原子性
- 这些指令保证了操作的原子性,即使在多核处理器环境下也不会被中断。
2、Unsafe 类:Java 的底层'魔法工具'
Java 并未直接暴露 CPU 指令,而是通过 sun.misc.Unsafe 类提供的 native 方法封装底层操作。Unsafe 类是 JVM 内部使用的'危险类',允许直接操作内存地址和调用底层指令,其提供的 CAS 相关方法是原子类的核心依赖:
public final class Unsafe {
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);
}
这些方法通过 native 关键字调用 C++ 内联汇编代码,最终触发对应 CPU 架构的原子指令。需要注意的是,Unsafe 类不建议普通开发者直接使用,其操作无安全校验,可能引发内存越界等严重问题。
3、volatile 关键字:保证内存可见性
- CAS 操作依赖变量的实时可见性,否则线程可能读取到过期值。原子类中的共享变量均通过
volatile 修饰
- 可见性:一个线程对变量的修改会立即刷新到主内存,其他线程读取时直接从主内存获取,避免缓存一致性问题
- 禁止指令重排:确保变量操作的顺序与代码逻辑一致,避免 CAS 操作被重排导致的逻辑错乱
三、CAS 的核心问题与解决方案
1、ABA 问题:值的'伪不变'陷阱
ABA 问题是 CAS 最经典的逻辑漏洞:变量从 A 被修改为 B,随后又被改回 A,CAS 操作会认为'值未变化'而成功执行,但中间的状态变更可能导致依赖变量状态的逻辑出错。
- (链表操作中的 ABA 风险) 假设单向链表初始状态为
A→B,两个线程同时执行'删除头节点'操作
- Thread1 读取头节点为 A、下一个节点为 B,准备执行 CAS 更新头节点为 B,但被阻塞
- Thread2 成功删除头节点 A,链表变为
B;随后新增节点 A,链表变为 A→B(新 A 节点)
- Thread1 恢复运行,CAS 检查头节点仍为 A,执行更新将头节点设为 B,错误删除新 A 节点的下一个节点 B,导致链表结构损坏
解决方案:不仅校验变量值,还校验其'版本',确保值的变化轨迹可追溯。Java 中提供两类原子类实现该方案。
- AtomicStampedReference(版本戳记引用):为对象引用绑定一个 int 类型的版本号(stamp),每次修改时同时更新值和版本号(版本号递增)。CAS 操作需同时匹配'值'和'版本号'才会成功,即使值回滚,版本号也会变化,从而避免 ABA 问题
AtomicStampedReference<String> asr = new AtomicStampedReference<>("A", 1);
int oldStamp = asr.getStamp();
String oldValue = asr.getReference();
boolean success = asr.compareAndSet(oldValue, "B", oldStamp, oldStamp + 1);
2、自旋开销问题:高冲突下的性能损耗
CAS 失败时会通过'自旋'(循环重试 CAS 操作),若并发冲突剧烈,大量线程会持续自旋,占用 CPU 资源,导致性能下降。
解决方案:
- 自适应自旋:JVM 可根据历史自旋成功率动态调整自旋次数,成功率高则增加次数,反之减少
- 分段锁优化:如 LongAdder(后续原子类部分详解),将变量拆分为多个分段,线程仅操作对应分段,降低冲突概率
- 冲突阈值控制:当自旋次数超过阈值时,降级为阻塞锁,避免 CPU 空耗
自旋锁:线程拿不到锁就循环重试,无上下文切换开销,但消耗 CPU。适合短耗时操作
阻塞锁:线程拿不到锁就休眠,不消耗 CPU,但有巨大的上下文切换开销。适合长耗时操作
3、只能保证单个变量的原子操作
CAS 仅能对单个变量实现原子操作,无法直接保证多个变量组合操作的原子性(如'更新变量 a 同时更新变量 b')。
解决方案:
- 将多个变量封装为一个对象,通过 AtomicReference 原子更新对象引用,间接实现组合操作的原子性
- 必要时使用锁机制(如 ReentrantLock)包裹多个 CAS 操作,牺牲部分性能换取组合原子性
四、Java 原子类:CAS 的上层封装与实战
Java 并发包(java.util.concurrent.atomic)提供了一系列原子类,基于 sun.misc.Unsafe 类封装了常用原子操作,无需开发者直接操作底层指令。根据操作目标类型,原子类可分为四大类。
1、基本类型原子类
- 针对 int、long、boolean 三种基本类型,提供对应的原子类,核心方法包括自增、自减、CAS 更新等
- AtomicInteger:原子更新 int 类型值
- AtomicLong:原子更新 long 类型值
- AtomicBoolean:原子更新 boolean 类型值(底层通过 int 类型转换实现,0 表示 false,1 表示 true)
AtomicInteger - 原子更新 int 类型
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicIntegerExample {
public static void main(String[] args) {
AtomicInteger atomicInt = new AtomicInteger(0);
System.out.println("初始值:" + atomicInt.get());
atomicInt.set(10);
System.out.println("设置后:" + atomicInt.get());
atomicInt.incrementAndGet();
System.out.println("incrementAndGet 后:" + atomicInt.get());
atomicInt.getAndIncrement();
System.out.println("getAndIncrement 后:" + atomicInt.get());
atomicInt.decrementAndGet();
System.out.println("decrementAndGet 后:" + atomicInt.get());
atomicInt.getAndDecrement();
System.out.println("getAndDecrement 后:" + atomicInt.get());
atomicInt.addAndGet(5);
System.out.println("addAndGet(5) 后:" + atomicInt.get());
int oldValue atomicInt.getAndAdd();
System.out.println( + oldValue + + atomicInt.get());
atomicInt.compareAndSet(, );
System.out.println( + success + + atomicInt.get());
atomicInt.updateAndGet(x -> x * );
System.out.println( + newValue);
}
}
核心源码解析(以 AtomicInteger 为例)
public class AtomicInteger {
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
private volatile int value;
static {
try {
valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) {
throw new Error(ex);
}
}
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
public {
getAndAddInt() + ;
}
}
其中,Unsafe.getAndAddInt 方法底层通过 do-while 循环实现自旋
public final class Unsafe {
public native int getIntVolatile(Object o, long offset);
public final native boolean compareAndSwapInt(Object o, long offset, int expected, int x);
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
v = getIntVolatile(o, offset);
} while (!compareAndSwapInt(o, offset, v, v + delta));
return v;
}
}
compareAndSwapInt 工作原理(伪代码)
boolean compareAndSwapInt(Object o, long offset, int expected, int x) {
int* address = (int*)((char*)o + offset);
int current = *address;
if (current == expected) {
*address = x;
return true;
} else {
return false;
}
}
⚠️ 注意:上面的伪代码不是原子的!真正的 compareAndSwapInt 是由 CPU 在硬件层面保证整个操作(比较 + 交换)是原子的,不会被线程调度打断
2、数组类型原子类
- 针对数组元素的原子操作,提供三类原子类,支持通过索引原子更新数组中的元素,底层同样依赖 CAS 机制
- AtomicIntegerArray:原子更新 int 数组元素
- AtomicLongArray:原子更新 long 数组元素
- AtomicReferenceArray:原子更新引用类型数组元素
AtomicIntegerArray array = new AtomicIntegerArray(new int[]{1, 2, 3});
array.compareAndSet(0, 1, 5);
array.incrementAndGet(1);
System.out.println(array.get(0));
System.out.println(array.get(1));
3、对象引用类型原子类
针对对象引用的原子操作,除了前文解决 ABA 问题的 AtomicStampedReference(比较引用值 + 版本戳),还包括基础的 AtomicReference(比较引用值)和 AtomicMarkableReference(比较引用值 + 标记位),用于原子更新对象引用本身。
- AtomicReference:基本的引用原子更新
class Data {
private String content;
}
public class AtomicReferenceDemo {
private static final AtomicReference<Data> cacheRef = new AtomicReference<>(new Data("初始缓存"));
public static void updateCache(Data newData) {
cacheRef.compareAndSet(cacheRef.get(), newData);
}
public static void main(String[] args) {
Data newCache = new Data("更新后缓存");
updateCache(newCache);
System.out.println(cacheRef.get().getContent());
}
}
- AtomicMarkableReference:为对象引用绑定一个 boolean 类型的标记(mark),仅表示'值是否被修改过',不记录修改次数
AtomicMarkableReference<String> amr = new AtomicMarkableReference<>("A", false);
boolean[] markHolder = new boolean[1];
String oldValue = amr.get(markHolder);
boolean success = amr.compareAndSet(oldValue, "B", false, true);
4、对象字段更新原子类
- 针对对象的指定字段(非静态字段)实现原子更新,无需修改字段所属类的代码,灵活性极高
- 需满足两个条件:字段必须是 volatile 修饰的(保证可见性),且不能是 private 字段(封装性保护)
- AtomicIntegerFieldUpdater:原子更新对象的 volatile int 字段
- AtomicLongFieldUpdater:原子更新对象的 volatile long 字段
- AtomicReferenceFieldUpdater:原子更新对象的 volatile 引用字段
public class BankAccount {
private String name;
private volatile int balance;
private static final AtomicIntegerFieldUpdater<BankAccount> updater =
AtomicIntegerFieldUpdater.newUpdater(BankAccount.class, "balance");
public void deposit(int amount) {
updater.addAndGet(this, amount);
}
public int increment() {
updater.incrementAndGet(this);
}
public int getBalance() {
return updater.get(this);
}
}
5、性能优化原子类:LongAdder 与 DoubleAdder
在高并发计数场景下,AtomicLong 的自旋 CAS 会因冲突剧烈导致性能下降。JDK 1.8 引入 LongAdder 和 DoubleAdder,通过'分段锁'思想优化性能。
核心原理
- LongAdder 将计数器拆分为多个'单元格'(Cell 数组),每个线程优先操作自己对应的单元格(通过线程哈希定位),降低 CAS 冲突概率
- 最终获取结果时,累加所有单元格的值与基础值(base)
- 当并发量较低时,直接操作 base 值,与 AtomicLong 性能接近
- 并发量较高时,单元格分散冲突,性能远超 AtomicLong
实战对比:LongAdder 与 AtomicLong 性能
public class LongAdderVsAtomicLong {
private static final AtomicLong atomicLong = new AtomicLong();
private static final LongAdder longAdder = new LongAdder();
private static final int THREAD_COUNT = 20;
private static final int LOOP_COUNT = 1000000;
public static void main(String[] args) throws InterruptedException {
ExecutorService executor1 = Executors.newFixedThreadPool(THREAD_COUNT);
long start1 = System.currentTimeMillis();
for (int i = 0; i < THREAD_COUNT; i++) {
executor1.submit(() -> {
for (int j ; j < LOOP_COUNT; j++) {
atomicLong.incrementAndGet();
}
});
}
executor1.shutdown();
executor1.awaitTermination(, TimeUnit.MINUTES);
System.currentTimeMillis() - start1;
System.out.println( + cost1 + + atomicLong.get());
Executors.newFixedThreadPool(THREAD_COUNT);
System.currentTimeMillis();
( ; i < THREAD_COUNT; i++) {
executor2.submit(() -> {
( ; j < LOOP_COUNT; j++) {
longAdder.increment();
}
});
}
executor2.shutdown();
executor2.awaitTermination(, TimeUnit.MINUTES);
System.currentTimeMillis() - start2;
System.out.println( + cost2 + + longAdder.sum());
}
}
运行结果通常显示:高并发下 LongAdder 耗时仅为 AtomicLong 的 1/3~1/2,适合海量并发计数场景
五、CAS 与原子类的适用场景与对比
1、适用场景
- 低冲突、短耗时操作:如计数器、状态标志位更新,CAS 无锁特性可最大化性能
- 高并发计数/累加:优先使用 LongAdder/DoubleAdder,分段锁设计适配高冲突场景
- 无锁数据结构实现:如无锁队列、无锁栈,基于 AtomicReference 封装 CAS 操作,提升并发吞吐量
- 对象字段/引用的原子更新:使用字段更新原子类或引用类型原子类,无需修改原有类结构,灵活性高
2、与悲观锁(synchronized/ReentrantLock)的对比
| 对比维度 | CAS(乐观锁)/原子类 | 悲观锁 |
|---|
| 核心思想 | '先操作,后检测冲突'(假设冲突很少发生) | '先加锁,再操作'(假设冲突总发生) |
| 线程行为 | 无阻塞,冲突时自旋重试 | 竞争失败时线程挂起(上下文切换) |
| 性能开销 | 轻量级,没有锁竞争和唤醒开销 | 重量级,含上下文切换、锁调度开销 |
| 冲突处理 | 高冲突下自旋耗 CPU,性能下降 | 高冲突下稳定性更好,阻塞避免 CPU 空耗 |
| 功能范围 | 仅支持单个变量/字段的原子操作 | 支持任意代码块的原子性,功能更全面 |
| ABA 问题 | 存在,需额外机制解决 | 不存在,互斥访问避免中间状态变更 |