弗兰的悲惨之旅
99.73M · 2026-04-04
## 前言
在Java高并发编程中,CAS是无锁并发的核心基石,也是AQS、原子类、ConcurrentHashMap等JUC核心组件的底层依赖。多数开发者仅停留在AtomicInteger的API使用层面,对其底层实现、原子性保障原理一知半解,生产环境中频繁踩中ABA、自旋CPU飙升、伪共享等致命坑。本文从CPU原语、JDK17源码、生产实战、踩坑避坑全链路拆解CAS,帮你彻底吃透无锁并发的核心逻辑。
CAS全称Compare-And-Swap(比较并交换),是一种硬件级别的原子操作原语,核心语义是:针对内存地址V,给定旧预期值A与新值B,当且仅当V的当前值等于A时,才将V的值原子更新为B,整个操作不可中断。
它是乐观锁的核心实现,区别于synchronized等悲观锁的“先加锁再操作”,CAS采用“先验证再更新”的无锁思路,在低并发场景下大幅降低线程调度与上下文切换的开销。
要彻底理解CAS,必须先明确两个核心基础:
CAS的原子性本质是CPU硬件层面的指令支持,不同CPU架构有不同实现,以主流X86_64架构为例: CAS的核心是CMPXCHG指令(比较并交换指令),但该指令本身不具备多核原子性,必须搭配LOCK前缀才能实现多核环境下的原子操作。
LOCK前缀的核心作用:
极端情况下(操作数据跨缓存行),LOCK前缀会降级为锁总线,保证操作的原子性,但性能开销会显著提升。
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表示更新失败。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更新失败,说明当前值被其他线程修改,重新获取最新值再次尝试,直到更新成功。
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;
}
}
我们基于真实业务场景,实现3个高复用的CAS实战案例,代码严格遵循《阿里巴巴Java开发手册》规范。
业务场景:高并发场景下的接口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。
业务场景:分布式锁的本地自旋优化、短执行时间的临界区资源保护,避免线程上下文切换开销。 完整代码实现:
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);
}
}
业务场景:账户余额修改、库存扣减等需要严格校验数据版本的场景,避免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();
}
}
问题现象:线程1读取变量值为A,此时线程2将变量值从A改为B,再改回A,线程1执行CAS时发现值还是A,认为数据未被修改,执行更新成功,导致业务逻辑错误。典型场景包括库存扣减、账户余额修改、链表结构修改等,ABA问题会导致数据丢失、业务状态异常。 根因分析:CAS仅校验变量的当前值,未校验变量的变更过程,无法识别数据是否被修改过。 避坑方案:
AtomicStampedReference,给变量附加一个递增的版本号,CAS操作必须同时匹配值与版本号,只要数据被修改过,版本号就会递增,彻底解决ABA问题。AtomicMarkableReference,附加一个boolean类型的标记位,记录变量是否被修改过。问题现象:高并发场景下,使用CAS实现的计数器、自旋锁出现CPU使用率持续100%,业务接口响应超时。 根因分析:CAS更新失败时会进入自旋循环,高并发下大量线程同时竞争同一个变量,CAS更新成功率极低,导致线程持续占用CPU自旋,耗尽CPU资源。 避坑方案:
LongAdder/DoubleAdder替代AtomicLong,通过分段数组分散竞争,将单点CAS竞争分散到多个Cell元素,大幅提升高并发下的性能。问题现象:多个CAS操作的变量位于同一个CPU缓存行中,多线程同时修改这些变量时,频繁出现缓存行失效,CAS性能下降数十倍。 根因分析:CPU缓存的最小单位是缓存行(通常64字节),当多个变量位于同一个缓存行时,一个变量的修改会导致整个缓存行失效,其他CPU核心需要重新从主内存加载数据,即伪共享问题。CAS操作频繁修改变量,会放大伪共享的性能影响。 避坑方案:
@sun.misc.Contended注解,自动实现缓存行填充,JDK 17中需要添加JVM启动参数-XX:-RestrictContended才能生效。问题现象:32位JVM/操作系统中,对long/double类型变量执行CAS操作时,出现更新异常,原子性无法保障。 根因分析:JVM规范中,64位的long/double类型变量的读写操作分为两个32位的操作执行,不保证原子性。32位环境下,CAS操作无法保障64位变量的单次读写原子性,导致CAS校验失败。 避坑方案:
AtomicLong/AtomicDouble,底层已处理原子性问题。问题现象:需要同时更新多个字段时,分别对每个字段执行CAS操作,导致原子性无法保障,出现数据不一致问题。 根因分析:单次CAS操作只能保证单个变量的原子性,多个CAS操作无法构成原子操作,中间可能被其他线程打断,导致部分字段更新成功,部分更新失败。 避坑方案:
AtomicReference对整个对象执行CAS操作,保证多字段更新的原子性。CAS不是银弹,必须明确其适用场景,才能发挥最大性能优势:
| 场景类型 | 推荐方案 | 核心原因 |
|---|---|---|
| 低并发、短执行时间临界区 | CAS无锁方案 | 无线程上下文切换开销,性能远超悲观锁 |
| 高并发、热点变量竞争 | LongAdder分段CAS | 分散竞争,避免单点自旋,性能提升10倍以上 |
| 长执行时间临界区、复杂业务逻辑 | synchronized/Lock悲观锁 | 避免长时间自旋耗尽CPU资源 |
| 多字段原子更新 | 悲观锁/AtomicReference封装 | 保证原子性,避免数据不一致 |
CAS是整个JUC并发包的基石,JDK中大量核心组件都基于CAS实现:
本文从CPU原语、JDK 17源码、生产实战、踩坑避坑全链路,深度拆解了CAS无锁并发的核心逻辑。 核心要点总结:
CAS是Java并发编程的核心基本功,只有彻底吃透其底层原理与边界,才能在生产环境中写出高性能、高稳定的并发代码。后续我们会继续拆解AQS、ConcurrentHashMap等JUC核心组件的CAS实现,欢迎持续关注。
作者:果酱 专注Java核心技术、分布式架构、性能优化与生产实战。 本文原创首发于阿里云,公众号,CSDN,稀土掘金,未经授权禁止任何形式的转载、抄袭与洗稿。觉得文章有帮助的同学,欢迎点赞 收藏⭐ 关注。