在Java并发编程中,我们常面临多线程竞争共享资源的问题,传统的synchronized锁虽能保证线程安全,但阻塞式的特性会带来一定的性能开销。而CAS(Compare-And-Swap,比较并交换)作为无锁并发的核心技术,凭借其轻量级、高效的特点,成为实现原子类、轻量级锁、AQS等并发组件的底层基石。本文将从CAS的核心定义、底层实现、工作机制、应用场景,到常见问题,全方位拆解CAS,帮你彻底搞懂它的本质与价值。
一、什么是CAS?核心定义与本质
CAS,全称Compare-And-Swap,即"比较并交换",是一种无锁同步机制,它通过硬件指令保证"读取-比较-修改"三步操作的原子性,无需使用synchronized等阻塞锁,就能实现多线程环境下的安全并发。
CAS的核心逻辑可以概括为三个关键参数:
-
内存地址(Memory Address):存储目标共享变量的内存位置;
-
预期旧值(Expected Value):线程获取到的共享变量当前值;
-
新值(New Value):线程想要将共享变量修改为的值。
CAS的执行流程极简且原子:线程先从指定内存地址读取当前值,与自己持有的预期旧值进行比较;若两者相等,说明该变量未被其他线程修改,立即将其更新为新值;若不相等,说明变量已被其他线程篡改,放弃修改并返回失败,线程可选择重试或退出。
一句话总结CAS的本质:"先确认,再修改",通过硬件保证操作的原子性,避免多线程并发修改导致的数据错乱,同时摆脱阻塞锁的性能损耗。
二、CAS底层实现:从Java层到硬件层
很多人误以为CAS是Java层面的API,实则其核心实现依赖CPU硬件指令,Java只是通过native方法封装了底层逻辑,保证跨平台兼容性。
1. Java层:Unsafe类的核心API
在Java中,CAS操作主要通过sun.misc.Unsafe类提供的native方法实现,这是一个直接操作内存、绕过JVM安全检查的底层工具类,其CAS相关方法如下(以int类型为例):
java
/**
* CAS操作核心方法
* @param o 要修改的对象
* @param offset 对象中目标字段的内存偏移量(定位字段在内存中的位置)
* @param expected 预期旧值
* @param newValue 要修改的新值
* @return 修改成功返回true,失败返回false
*/
public final native boolean compareAndSwapInt(Object o, long offset, int expected, int newValue);
}
关键点说明:
-
native方法:底层由C++实现,最终调用CPU指令,保证操作原子性;
-
内存偏移量(offset):通过Unsafe的
objectFieldOffset()方法获取,用于精准定位共享变量在对象内存中的位置; -
无锁特性:方法执行时无需加锁,失败后不会阻塞线程,仅返回false。
2. 底层硬件实现:CPU原子指令
CAS的原子性并非Java层面实现,而是依赖CPU的单条原子指令,不同架构的CPU指令不同:
-
x86架构:使用
cmpxchg指令(Compare and Exchange),该指令会自动完成"读取-比较-修改"三步操作,且全程不可中断; -
ARM架构:使用
ldrex/strex指令组合,实现类似的原子操作。
CPU层面的原子指令,是CAS无锁安全的根本保障------单条指令的执行不会被其他线程打断,避免了多线程并发时的"竞态条件"(Race Condition)。
3. 简化底层伪代码(理解核心逻辑)
为了更直观理解,我们用伪代码模拟CAS的底层执行逻辑(忽略硬件指令细节,聚焦核心流程):
java
// 底层CAS伪代码(C++风格)
bool compareAndSwapInt(void* memoryAddr, int expected, int newValue) {
// 1. 从指定内存地址读取当前真实值(原子操作)
int realValue = *memoryAddr;
// 2. 比较真实值与预期旧值
if (realValue == expected) {
// 3. 相等则替换为新值(原子操作)
*memoryAddr = newValue;
return true; // 修改成功
}
// 4. 不相等则不修改,返回失败
return false;
}
注意:上述伪代码中,"读取真实值"和"修改新值"并非单独的操作,而是被CPU指令合并为一个不可分割的原子操作,这也是CAS区别于普通"读取-修改"操作的核心。
三、CAS工作机制:自旋CAS与锁升级关联
CAS的核心优势是"无锁",但当多个线程同时竞争时,必然会有线程CAS失败。此时,失败的线程通常会选择"自旋重试",这也是轻量级锁的核心实现逻辑------结合你之前关注的锁升级知识点,我们串联起来理解:
1. 自旋CAS:失败不阻塞,重试直到成功
当线程CAS失败后,不会进入阻塞状态(区别于synchronized的阻塞锁),而是在原地循环重试,直到CAS成功。这种"循环重试"的机制,就是自旋CAS,也是Atomic系列原子类的核心实现方式。
以AtomicInteger的getAndIncrement()方法(自增操作)为例,其底层就是自旋CAS:
java
public class AtomicInteger extends Number implements java.io.Serializable {
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset; // 变量value的内存偏移量
static {
try {
// 获取value字段的内存偏移量
valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
private volatile int value; // 共享变量,volatile保证可见性
// 自增操作:底层自旋CAS
public final int getAndIncrement() {
int oldValue, newValue;
do {
// 1. 获取当前值(volatile保证可见性,拿到最新值)
oldValue = get();
// 2. 计算新值
newValue = oldValue + 1;
// 3. CAS尝试修改,失败则重试
} while (!unsafe.compareAndSwapInt(this, valueOffset, oldValue, newValue));
return oldValue;
}
}
自旋CAS的优势:轻量级,无需切换线程状态(线程阻塞/唤醒会消耗大量CPU资源),适合并发竞争不激烈的场景;
自旋CAS的劣势:若并发竞争激烈,线程会一直自旋重试,导致CPU空转,消耗大量资源------这也是为什么轻量级锁自旋次数达到阈值后,会升级为重量级锁(避免CPU空转)。
2. 与锁升级的关联(衔接之前知识点)
结合你之前关注的"锁状态演变",CAS在锁升级中扮演着核心角色:
-
偏向锁获取:线程第一次获取锁时,通过CAS将线程ID写入对象头MarkWord,后续线程直接对比ID,无需再次CAS;
-
偏向锁撤销→轻量级锁:当有第二个线程竞争时,通过CAS撤销偏向锁,线程在栈帧中生成LockRecord,再通过CAS替换对象头指针,获取轻量级锁;
-
轻量级锁→重量级锁:自旋CAS重试次数达到阈值(默认10次),或有更多线程竞争,CAS失败后升级为重量级锁,避免CPU空转。
可以说,CAS是无锁、偏向锁、轻量级锁的核心支撑,只有当CAS无法高效解决竞争时,才会升级为重量级锁,实现"轻量竞争用CAS,激烈竞争用阻塞锁"的自适应优化。
四、CAS的应用场景:哪里会用到CAS?
CAS作为无锁并发的基础,在Java并发体系中应用广泛,核心场景主要有三类:
1. Atomic系列原子类(最直接应用)
Java.util.concurrent.atomic包下的所有原子类,底层均基于CAS实现,用于解决多线程下基本数据类型、引用类型的安全修改,无需手动加锁:
-
AtomicInteger/AtomicLong:解决int/long类型的原子自增、自减、赋值等操作;
-
AtomicBoolean:解决boolean类型的原子修改;
-
AtomicReference:解决对象引用的原子修改(如原子更新对象地址);
-
AtomicStampedReference:解决CAS的ABA问题(后续讲解)。
示例:多线程自增,用AtomicInteger避免数据错乱(无需synchronized):
java
public class AtomicDemo {
private static final AtomicInteger count = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
// 10个线程,每个线程自增1000次
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
executor.submit(() -> {
for (int j = 0; j < 1000; j++) {
count.getAndIncrement(); // 原子自增,无锁安全
}
});
}
executor.shutdown();
executor.awaitTermination(1, TimeUnit.SECONDS);
System.out.println("最终计数:" + count.get()); // 必然输出10000
}
}
2. 轻量级锁与偏向锁(JVM锁机制)
如前所述,JVM的锁升级过程(无锁→偏向锁→轻量级锁→重量级锁)中,CAS是核心操作,用于实现锁的获取、撤销与升级,减少阻塞带来的性能损耗。
3. AQS框架(并发工具的底层)
Java中的锁(如ReentrantLock)、同步工具(如CountDownLatch、Semaphore),其底层均基于AQS(AbstractQueuedSynchronizer)实现,而AQS的核心同步机制,就是通过CAS操作修改同步状态(state变量),实现线程的排队与唤醒。