Java CAS 详解
CAS 是 Compare And Swap(比较并交换) 的缩写,是 Java 实现无锁并发 的核心原理,也是 java.util.concurrent.atomic 包(原子类)的底层实现机制,用来在不使用 synchronized 的情况下,保证多线程修改共享变量的线程安全
一、CAS 是什么
先比较内存中的值和预期值是否一致,如果一致,就把新值替换进去;如果不一致,说明有其他线程修改过,本次操作失败,不做替换
整个比较 + 替换 是一个原子操作(CPU 指令级保证,不可中断),所以多线程下不会出现数据错乱
二、CAS与乐观锁
乐观锁(Optimistic Lock)是一种并发控制思想
它的核心假设是:数据冲突很少发生
因此:
不加锁
先操作
提交时再检查是否有人修改过
如果发现冲突:
失败
重试
例如:
账户余额 = 100
线程A:
读取余额100
线程B:
读取余额100
A扣款:
100 → 90
提交成功
B也准备扣款:
100 → 80
此时检查发现:
余额已经不是100
说明:有人改过,操作失败
重新读取再计算
这就是乐观锁思想:先干活,最后检查
三、CAS 执行流程
CAS 操作需要 3 个值:
CAS(V, E, N)
V:内存中的实际值(Value)
E:期望值(Expected)
N:要更新的新值(New)
举例:
java
AtomicInteger count = new AtomicInteger(10);
线程A执行:
java
count.compareAndSet(10, 11);
内部过程:
当前值 = 10
期望值 = 10
新值 = 11
当前值10 == 期望值10
更新成功
结果:
java
count = 11
如果线程B同时执行:
java
count.compareAndSet(10, 12);
此时:
当前值 = 11
期望值 = 10
当前值11 != 期望值10
更新失败
返回:false
四、CAS 的底层实现
- Java 层面:
sun.misc.Unsafe类提供 CAS 方法 - 底层:CPU 原语指令 (如
cmpxchg),由硬件保证原子性 - 特点:无锁、轻量级、不会阻塞线程
Java 的 CAS 最终调用 JVM 的 Native 方法
例如:AtomicInteger
源码(JDK8):
java
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(
this,
valueOffset,
expect,
update);
}
这里调用:
java
Unsafe.compareAndSwapInt()
JDK9以后:
java
VarHandle.compareAndSet()
逐步替代 Unsafe
五、CAS 经典应用:原子类
Java 的 AtomicInteger、AtomicBoolean、AtomicLong 全是基于 CAS 实现的
示例代码:
java
import java.util.concurrent.atomic.AtomicInteger;
public class CASDemo {
// 原子整数,线程安全
private static AtomicInteger count = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) count.incrementAndGet(); // 底层就是 CAS
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) count.incrementAndGet();
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(count.get()); // 一定输出 2000,线程安全
}
}
incrementAndGet() 底层就是循环 CAS,直到修改成功
六、CAS 的优点
- 轻量级:不需要加锁、解锁,没有线程阻塞 / 唤醒开销
- 性能高:高并发、冲突少的场景,远优于 synchronized
- 无死锁:因为根本不加锁
七、CAS 的 3 大缺点
1. 自旋消耗 CPU(循环 CAS)
如果并发冲突严重,CAS 会一直循环重试,长时间占用 CPU
java
// 底层伪代码
do {
读取当前值 V;
计算新值 B;
} while (!CAS(V, B)); // 失败就无限重试
2. 只能保证一个变量的原子操作
CAS 一次只能操作一个共享变量,无法像锁一样同时保证多个变量的原子性。(解决方案:AtomicReference 包装成对象)
3. ABA 问题
场景:
- 内存值 V = A
- 线程 1 把 A → B
- 线程 2 又把 B → A
- 线程 3 执行 CAS:发现 V 还是 A,以为没被修改过,直接修改成功
问题:变量被修改过又改回来,CAS 无法感知
解决方案 :版本号机制 (AtomicStampedReference)每次修改都带上版本号,比较时不仅比值,还要比版本号
八、CAS vs synchronized
| 特性 | CAS | synchronized |
|---|---|---|
| 实现 | 无锁,CPU 原子指令 | 阻塞锁,JVM 底层实现 |
| 原子性 | 单个变量 | 代码块 / 方法 |
| 阻塞 | 不阻塞线程 | 阻塞线程 |
| 性能 | 低冲突时极高 | 高冲突时更稳定 |
| 适用场景 | 原子变量、简单并发 | 复杂业务、多变量同步 |