## 前言
在Java高并发编程中,CAS是无锁并发的核心基石,也是AQS、原子类、ConcurrentHashMap等JUC核心组件的底层依赖。多数开发者仅停留在AtomicInteger的API使用层面,对其底层实现、原子性保障原理一知半解,生产环境中频繁踩中ABA、自旋CPU飙升、伪共享等致命坑。本文从CPU原语、JDK17源码、生产实战、踩坑避坑全链路拆解CAS,帮你彻底吃透无锁并发的核心逻辑。
一、CAS基础认知与核心背景
1.1 什么是CAS
CAS全称Compare-And-Swap(比较并交换),是一种硬件级别的原子操作原语,核心语义是:针对内存地址V,给定旧预期值A与新值B,当且仅当V的当前值等于A时,才将V的值原子更新为B,整个操作不可中断。
它是乐观锁的核心实现,区别于synchronized等悲观锁的“先加锁再操作”,CAS采用“先验证再更新”的无锁思路,在低并发场景下大幅降低线程调度与上下文切换的开销。
1.2 核心前置知识铺垫
要彻底理解CAS,必须先明确两个核心基础:
- CPU原子操作保障:现代多核CPU通过缓存一致性协议(如MESI)、总线锁/缓存行锁,保障单个内存操作的原子性。
- JMM内存模型:CAS操作同时具备volatile的读写内存语义,保证变量的可见性与禁止指令重排序,解决多线程下的内存不可见问题。
二、CAS底层核心原理解析(JDK 17源码)
2.1 CPU层面的原子性实现
CAS的原子性本质是CPU硬件层面的指令支持,不同CPU架构有不同实现,以主流X86_64架构为例: CAS的核心是CMPXCHG指令(比较并交换指令),但该指令本身不具备多核原子性,必须搭配LOCK前缀才能实现多核环境下的原子操作。
LOCK前缀的核心作用:
- 锁定操作对应的内存地址的缓存行(基于MESI协议),避免多核CPU同时修改该内存地址;
- 禁止该指令与前后的读写指令重排序;
- 刷新写缓冲区,保证操作结果对所有CPU核心立即可见。
极端情况下(操作数据跨缓存行),LOCK前缀会降级为锁总线,保证操作的原子性,但性能开销会显著提升。
2.2 JDK中CAS的核心载体:Unsafe类
Java作为高级语言,无法直接操作内存地址,CAS操作完全依赖jdk.internal.misc.Unsafe类(JDK9后从sun.misc.Unsafe迁移)的native方法实现。
Unsafe类中CAS的核心方法定义(JDK 17):
// 针对int类型的CAS操作
public final native boolean compareAndSetInt(Object o, long offset, int expected, int x);
// 针对long类型的CAS操作
public final native boolean compareAndSetLong(Object o, long offset, long expected, long x);
// 针对引用类型的CAS操作
public final native boolean compareAndSetReference(Object o, long offset, Object expected, Object x);
方法参数说明:
o:目标对象offset:目标字段在对象中的内存偏移量(固定值,类加载时计算)expected:旧预期值x:待更新的新值 返回值:boolean类型,true表示更新成功,false表示更新失败。
2.3 原子类的CAS源码拆解(AtomicInteger为例)
JUC中的原子类是CAS最典型的应用,我们以JDK 17的AtomicInteger为例,拆解其底层CAS实现:
public class AtomicInteger extends Number implements java.io.Serializable {
private static final jdk.internal.misc.Unsafe U = jdk.internal.misc.Unsafe.getUnsafe();
// 计算value字段的内存偏移量
private static final long VALUE = U.objectFieldOffset(AtomicInteger.class, "value");
// 核心value字段,volatile修饰保证可见性
private volatile int value;
// 原子递增:i++的原子实现
public final int getAndIncrement() {
return U.getAndAddInt(this, VALUE, 1);
}
}
我们继续看getAndAddInt方法的核心逻辑,JDK 17中该方法为内置方法,等价于如下实现:
public final int getAndAddInt(Object o, long offset, int delta) {
int oldValue;
do {
// 循环获取内存中的最新值
oldValue = this.getIntVolatile(o, offset);
// CAS尝试更新:如果当前值等于oldValue,就更新为oldValue+delta
} while (!this.compareAndSetInt(o, offset, oldValue, oldValue + delta));
// 返回更新前的旧值
return oldValue;
}
这里的核心就是自旋CAS:如果CAS更新失败,说明当前值被其他线程修改,重新获取最新值再次尝试,直到更新成功。
2.4 JDK 17+ 推荐替代方案:VarHandle
JDK 9之后引入了VarHandle(变量句柄),作为Unsafe类的合法替代方案,解决了Unsafe类的类型不安全、权限不受控、模块化不兼容的问题。
VarHandle同样支持CAS操作,且性能与Unsafe相当,JDK 17中JUC源码已大量迁移至VarHandle实现。核心示例如下:
public class VarHandleCasDemo {
private volatile int value;
// 定义VarHandle实例
private static final VarHandle VALUE_HANDLE;
static {
try {
// 初始化VarHandle,绑定value字段
VALUE_HANDLE = MethodHandles.lookup()
.findVarHandle(VarHandleCasDemo.class, "value", int.class);
} catch (ReflectiveOperationException e) {
throw new ExceptionInInitializerError(e);
}
}
// 基于VarHandle实现CAS原子递增
public final int getAndIncrement() {
int oldValue;
do {
oldValue = (int) VALUE_HANDLE.getVolatile(this);
} while (!VALUE_HANDLE.compareAndSet(this, oldValue, oldValue + 1));
return oldValue;
}
}
三、CAS生产实战落地(JDK 17)
我们基于真实业务场景,实现3个高复用的CAS实战案例,代码严格遵循《阿里巴巴Java开发手册》规范。
3.1 实战场景1:基于CAS实现无锁并发计数器
业务场景:高并发场景下的接口QPS统计、订单量统计,对比synchronized锁与CAS无锁计数器的性能差异。 完整代码实现:
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 基于CAS实现的无锁并发计数器
* @author 果酱
*/
@Slf4j
public class CasLockFreeCounter {
private volatile int count;
private static final jdk.internal.misc.Unsafe U = jdk.internal.misc.Unsafe.getUnsafe();
private static final long COUNT_OFFSET;
static {
try {
COUNT_OFFSET = U.objectFieldOffset(CasLockFreeCounter.class, "count");
} catch (Exception e) {
throw new Error(e);
}
}
/**
* 原子递增计数
* @return 递增后的最新值
*/
public int increment() {
int oldCount;
int newCount;
do {
oldCount = U.getIntVolatile(this, COUNT_OFFSET);
newCount = oldCount + 1;
// 自旋CAS更新
} while (!U.compareAndSetInt(this, COUNT_OFFSET, oldCount, newCount));
return newCount;
}
/**
* 获取当前计数值
* @return 当前计数
*/
public int getCount() {
return U.getIntVolatile(this, COUNT_OFFSET);
}
// 性能测试对比
public static void main(String[] args) throws InterruptedException {
int threadNum = 100;
int incrementTimes = 10000;
ExecutorService executor = Executors.newFixedThreadPool(threadNum);
CountDownLatch countDownLatch = new CountDownLatch(threadNum);
CasLockFreeCounter counter = new CasLockFreeCounter();
long startTime = System.currentTimeMillis();
for (int i = 0; i < threadNum; i++) {
executor.submit(() -> {
try {
for (int j = 0; j < incrementTimes; j++) {
counter.increment();
}
} finally {
countDownLatch.countDown();
}
});
}
countDownLatch.await();
long endTime = System.currentTimeMillis();
log.info("CAS无锁计数器最终结果:{}", counter.getCount());
log.info("CAS无锁计数器耗时:{}ms", endTime - startTime);
executor.shutdown();
}
}
注意事项:JDK 17中运行该代码,需要添加JVM启动参数--add-opens java.base/jdk.internal.misc=ALL-UNNAMED,否则会抛出权限异常;生产环境推荐使用VarHandle替代Unsafe。
3.2 实战场景2:基于CAS实现可重入自旋锁
业务场景:分布式锁的本地自旋优化、短执行时间的临界区资源保护,避免线程上下文切换开销。 完整代码实现:
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.atomic.AtomicReference;
/**
* 基于CAS实现的可重入自旋锁
* @author 果酱
*/
@Slf4j
public class CasReentrantSpinLock {
// 持有锁的线程引用
private final AtomicReference<Thread> lockHolder = new AtomicReference<>();
// 重入计数
private volatile int holdCount = 0;
/**
* 加锁:自旋CAS获取锁
*/
public void lock() {
Thread currentThread = Thread.currentThread();
// 重入判断:当前线程已持有锁,直接计数+1
if (currentThread == lockHolder.get()) {
holdCount++;
return;
}
// 自旋CAS获取锁
while (!lockHolder.compareAndSet(null, currentThread)) {
// 自旋等待,空循环
}
holdCount = 1;
}
/**
* 解锁:CAS释放锁
*/
public void unlock() {
Thread currentThread = Thread.currentThread();
// 只有持有锁的线程才能解锁
if (currentThread != lockHolder.get()) {
throw new IllegalMonitorStateException("当前线程未持有该锁,无法解锁");
}
holdCount--;
// 重入计数为0时,真正释放锁
if (holdCount == 0) {
lockHolder.compareAndSet(currentThread, null);
}
}
// 测试验证
public static void main(String[] args) throws InterruptedException {
CasReentrantSpinLock spinLock = new CasReentrantSpinLock();
int[] count = {0};
int threadNum = 10;
int loopTimes = 10000;
Thread[] threads = new Thread[threadNum];
for (int i = 0; i < threadNum; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < loopTimes; j++) {
spinLock.lock();
try {
// 重入测试
spinLock.lock();
count[0]++;
} finally {
spinLock.unlock();
spinLock.unlock();
}
}
});
}
long startTime = System.currentTimeMillis();
for (Thread thread : threads) {
thread.start();
}
for (Thread thread : threads) {
thread.join();
}
long endTime = System.currentTimeMillis();
log.info("自旋锁最终计数结果:{}", count[0]);
log.info("自旋锁执行耗时:{}ms", endTime - startTime);
}
}
3.3 实战场景3:基于CAS解决ABA问题的版本号控制
业务场景:账户余额修改、库存扣减等需要严格校验数据版本的场景,避免ABA问题导致的业务数据错误。 完整代码实现:
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.atomic.AtomicStampedReference;
/**
* 基于AtomicStampedReference解决ABA问题的账户余额操作
* @author 果酱
*/
@Slf4j
public class CasAbaSolutionDemo {
// 账户余额,带版本号的原子引用,解决ABA问题
private final AtomicStampedReference<Integer> accountBalance;
public CasAbaSolutionDemo(Integer initBalance) {
// 初始化余额与初始版本号
this.accountBalance = new AtomicStampedReference<>(initBalance, 0);
}
/**
* 账户余额扣减
* @param amount 扣减金额
* @return 扣减是否成功
*/
public boolean deductBalance(Integer amount) {
if (amount <= 0) {
log.error("扣减金额必须大于0,当前金额:{}", amount);
return false;
}
int[] stampHolder = new int[1];
// 获取当前余额与当前版本号
Integer currentBalance = accountBalance.get(stampHolder);
int currentStamp = stampHolder[0];
if (currentBalance < amount) {
log.error("账户余额不足,当前余额:{},扣减金额:{}", currentBalance, amount);
return false;
}
// CAS更新:必须同时匹配余额与版本号,版本号+1
boolean result = accountBalance.compareAndSet(
currentBalance,
currentBalance - amount,
currentStamp,
currentStamp + 1
);
if (result) {
log.info("余额扣减成功,扣减金额:{},当前余额:{},最新版本号:{}",
amount, accountBalance.getReference(), accountBalance.getStamp());
} else {
log.warn("余额扣减失败,数据已被其他线程修改,当前版本号:{}", currentStamp);
}
return result;
}
// 测试ABA场景
public static void main(String[] args) throws InterruptedException {
CasAbaSolutionDemo account = new CasAbaSolutionDemo(1000);
// 线程1:先扣减500,再充值500,制造ABA场景
Thread thread1 = new Thread(() -> {
account.deductBalance(500);
// 模拟充值
int[] stampHolder = new int[1];
Integer balance = account.accountBalance.get(stampHolder);
account.accountBalance.compareAndSet(balance, balance + 500, stampHolder[0], stampHolder[0] + 1);
log.info("线程1完成ABA操作,当前余额:{},版本号:{}",
account.accountBalance.getReference(), account.accountBalance.getStamp());
});
// 线程2:基于版本号校验扣减,避免ABA问题
Thread thread2 = new Thread(() -> {
try {
// 等待线程1完成ABA操作
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
account.deductBalance(800);
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
}
}
四、CAS生产踩坑指南与避坑最佳实践
4.1 坑1:ABA问题导致业务数据异常
问题现象:线程1读取变量值为A,此时线程2将变量值从A改为B,再改回A,线程1执行CAS时发现值还是A,认为数据未被修改,执行更新成功,导致业务逻辑错误。典型场景包括库存扣减、账户余额修改、链表结构修改等,ABA问题会导致数据丢失、业务状态异常。 根因分析:CAS仅校验变量的当前值,未校验变量的变更过程,无法识别数据是否被修改过。 避坑方案:
- 版本号控制:使用
AtomicStampedReference,给变量附加一个递增的版本号,CAS操作必须同时匹配值与版本号,只要数据被修改过,版本号就会递增,彻底解决ABA问题。 - 标记位控制:对于仅需判断是否被修改过的场景,使用
AtomicMarkableReference,附加一个boolean类型的标记位,记录变量是否被修改过。 - 业务场景约束:对于单调递增/递减的场景(如自增ID、流水号),ABA问题不会产生业务影响,无需额外处理。
4.2 坑2:高并发下自旋过度导致CPU 100%飙升
问题现象:高并发场景下,使用CAS实现的计数器、自旋锁出现CPU使用率持续100%,业务接口响应超时。 根因分析:CAS更新失败时会进入自旋循环,高并发下大量线程同时竞争同一个变量,CAS更新成功率极低,导致线程持续占用CPU自旋,耗尽CPU资源。 避坑方案:
- 限定自旋次数:给自旋循环设置最大次数阈值,超过阈值后升级为悲观锁(如synchronized),避免无限自旋。
- 自适应自旋:参考JVM synchronized的自适应自旋优化,根据前一次自旋的成功情况,动态调整当前自旋次数。
- 分段CAS优化:使用JDK提供的
LongAdder/DoubleAdder替代AtomicLong,通过分段数组分散竞争,将单点CAS竞争分散到多个Cell元素,大幅提升高并发下的性能。 - 放弃自旋,使用阻塞机制:对于长执行时间的临界区,直接使用悲观锁,避免自旋带来的CPU开销。
4.3 坑3:伪共享问题导致CAS性能急剧下降
问题现象:多个CAS操作的变量位于同一个CPU缓存行中,多线程同时修改这些变量时,频繁出现缓存行失效,CAS性能下降数十倍。 根因分析:CPU缓存的最小单位是缓存行(通常64字节),当多个变量位于同一个缓存行时,一个变量的修改会导致整个缓存行失效,其他CPU核心需要重新从主内存加载数据,即伪共享问题。CAS操作频繁修改变量,会放大伪共享的性能影响。 避坑方案:
- 缓存行填充:在变量前后填充7个long类型变量(8字节*8=64字节),让单个变量独占一个缓存行。
- 使用@Contended注解:JDK 1.8+提供的
@sun.misc.Contended注解,自动实现缓存行填充,JDK 17中需要添加JVM启动参数-XX:-RestrictContended才能生效。 - 变量隔离:将频繁修改的CAS变量与其他变量分开存储,避免同处一个缓存行。
4.4 坑4:64位long/double变量的CAS非原子性问题
问题现象:32位JVM/操作系统中,对long/double类型变量执行CAS操作时,出现更新异常,原子性无法保障。 根因分析:JVM规范中,64位的long/double类型变量的读写操作分为两个32位的操作执行,不保证原子性。32位环境下,CAS操作无法保障64位变量的单次读写原子性,导致CAS校验失败。 避坑方案:
- volatile修饰:给long/double类型变量添加volatile修饰符,JVM会保证64位变量的读写操作的原子性。
- 使用封装原子类:直接使用
AtomicLong/AtomicDouble,底层已处理原子性问题。 - 64位环境优先:生产环境优先使用64位JVM与操作系统,从根本上避免该问题。
4.5 坑5:多字段原子更新的CAS误用
问题现象:需要同时更新多个字段时,分别对每个字段执行CAS操作,导致原子性无法保障,出现数据不一致问题。 根因分析:单次CAS操作只能保证单个变量的原子性,多个CAS操作无法构成原子操作,中间可能被其他线程打断,导致部分字段更新成功,部分更新失败。 避坑方案:
- 对象封装:将多个需要原子更新的字段封装成一个不可变对象,使用
AtomicReference对整个对象执行CAS操作,保证多字段更新的原子性。 - 加锁保障:对于多字段复杂更新场景,直接使用悲观锁,避免CAS误用带来的数据不一致问题。
五、CAS性能优化与进阶拓展
5.1 CAS的适用场景边界
CAS不是银弹,必须明确其适用场景,才能发挥最大性能优势:
| 场景类型 | 推荐方案 | 核心原因 |
|---|---|---|
| 低并发、短执行时间临界区 | CAS无锁方案 | 无线程上下文切换开销,性能远超悲观锁 |
| 高并发、热点变量竞争 | LongAdder分段CAS | 分散竞争,避免单点自旋,性能提升10倍以上 |
| 长执行时间临界区、复杂业务逻辑 | synchronized/Lock悲观锁 | 避免长时间自旋耗尽CPU资源 |
| 多字段原子更新 | 悲观锁/AtomicReference封装 | 保证原子性,避免数据不一致 |
5.2 JDK中CAS的核心应用场景
CAS是整个JUC并发包的基石,JDK中大量核心组件都基于CAS实现:
- 原子类:AtomicInteger、AtomicLong等原子类,全部基于自旋CAS实现原子操作。
- AQS抽象队列同步器:ReentrantLock、CountDownLatch、Semaphore等同步工具,基于CAS实现同步状态的原子更新、CLH队列的节点入队出队。
- ConcurrentHashMap:JDK 1.8+的ConcurrentHashMap,基于CAS实现数组元素的原子更新,替代了分段锁,大幅提升并发性能。
- 线程池:ThreadPoolExecutor基于CAS实现线程池状态的原子更新、工作线程的计数。
- LockSupport:配合CAS实现线程的阻塞与唤醒,是AQS的底层依赖。
5.3 CAS进阶优化方向
- 无锁并发数据结构:基于CAS实现无锁队列、无锁栈、无锁哈希表等数据结构,避免锁开销,提升高并发下的吞吐量。
- 混合锁机制:结合CAS与悲观锁,短时间内使用CAS自旋,超过阈值后升级为阻塞锁,兼顾低延迟与高并发稳定性。
- 硬件级优化:利用CPU的TSX(事务同步扩展)指令,实现硬件级别的事务内存,优化CAS的竞争开销。
- 分布式CAS:基于Redis的CAS操作(SETNX、WATCH+MULTI)、分布式锁的自旋优化,实现分布式环境下的无锁并发控制。
全文总结
本文从CPU原语、JDK 17源码、生产实战、踩坑避坑全链路,深度拆解了CAS无锁并发的核心逻辑。 核心要点总结:
- CAS是硬件级别的原子操作原语,X86架构下基于LOCK+CMPXCHG指令实现多核原子性,是Java无锁并发的核心基石。
- JDK中CAS的核心载体是Unsafe类,JDK 9+推荐使用类型安全的VarHandle作为替代方案。
- CAS的核心优势是无锁、低延迟,核心缺陷是ABA问题、自旋CPU开销、伪共享、单变量原子性限制。
- 生产环境中,必须根据业务场景选择合适的方案,高并发热点变量优先使用LongAdder,需要版本校验的场景使用AtomicStampedReference,避免踩中常见坑。
CAS是Java并发编程的核心基本功,只有彻底吃透其底层原理与边界,才能在生产环境中写出高性能、高稳定的并发代码。后续我们会继续拆解AQS、ConcurrentHashMap等JUC核心组件的CAS实现,欢迎持续关注。
有关果酱
作者:果酱 专注Java核心技术、分布式架构、性能优化与生产实战。 本文原创首发于阿里云,公众号,CSDN,稀土掘金,未经授权禁止任何形式的转载、抄袭与洗稿。觉得文章有帮助的同学,欢迎点赞👍 收藏⭐ 关注✅。