跳到主要内容
CAS 原理与 Java 原子类实战 | 极客日志
Java java 算法
CAS 原理与 Java 原子类实战 综述由AI生成 CAS 是一种无锁并发控制技术,基于 CPU 原子指令实现比较并交换。其核心包含内存位置、预期原值和目标新值,默认假设无冲突,失败则重试。主要问题包括 ABA 问题(可通过版本号解决)、自旋开销(高冲突下性能下降)及单变量限制。Java 原子类(如 AtomicInteger、LongAdder)封装了 Unsafe 操作,提供基本类型、数组、引用及字段更新支持。相比悲观锁,CAS 在低竞争下性能更优,适合计数器及无锁数据结构;高竞争时 LongAdder 通过分段优化优于 AtomicLong。
霸天 发布于 2026/2/26 更新于 2026/6/3 26 浏览引言
在高并发编程领域,线程安全是永恒的核心议题。传统的悲观锁机制(如 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(3 );
System.out.println("getAndAdd(3) - 旧值:" + oldValue + ", 当前值:" + atomicInt.get());
boolean success = atomicInt.compareAndSet(18 , 20 );
System.out.println("CAS 操作成功:" + success + ", 当前值:" + atomicInt.get());
int newValue = atomicInt.updateAndGet(x -> x * 2 );
System.out.println("updateAndGet(x->x*2) 结果:" + 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 final int incrementAndGet () {
return getAndAddInt(1 ) + 1 ;
}
}
其中,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 = 0 ; j < LOOP_COUNT; j++) {
atomicLong.incrementAndGet();
}
});
}
executor1.shutdown();
executor1.awaitTermination(1 , TimeUnit.MINUTES);
long cost1 = System.currentTimeMillis() - start1;
System.out.println("AtomicLong 耗时:" + cost1 + "ms,结果:" + atomicLong.get());
ExecutorService executor2 = Executors.newFixedThreadPool(THREAD_COUNT);
long start2 = System.currentTimeMillis();
for (int i = 0 ; i < THREAD_COUNT; i++) {
executor2.submit(() -> {
for (int j = 0 ; j < LOOP_COUNT; j++) {
longAdder.increment();
}
});
}
executor2.shutdown();
executor2.awaitTermination(1 , TimeUnit.MINUTES);
long cost2 = System.currentTimeMillis() - start2;
System.out.println("LongAdder 耗时:" + cost2 + "ms,结果:" + longAdder.sum());
}
}
运行结果通常显示:高并发下 LongAdder 耗时仅为 AtomicLong 的 1/3~1/2,适合海量并发计数场景
五、CAS 与原子类的适用场景与对比
1、适用场景
低冲突、短耗时操作 :如计数器、状态标志位更新,CAS 无锁特性可最大化性能
高并发计数/累加 :优先使用 LongAdder/DoubleAdder,分段锁设计适配高冲突场景
无锁数据结构实现 :如无锁队列、无锁栈,基于 AtomicReference 封装 CAS 操作,提升并发吞吐量
对象字段/引用的原子更新 :使用字段更新原子类或引用类型原子类,无需修改原有类结构,灵活性高
2、与悲观锁(synchronized/ReentrantLock)的对比 对比维度 CAS(乐观锁)/原子类 悲观锁 核心思想 '先操作,后检测冲突' (假设冲突很少发生)'先加锁,再操作' (假设冲突总发生)线程行为 无阻塞 ,冲突时自旋重试竞争失败时线程挂起(上下文切换) 性能开销 轻量级 ,没有锁竞争和唤醒开销重量级 ,含上下文切换、锁调度开销冲突处理 高冲突下自旋耗 CPU,性能下降 高冲突下稳定性更好,阻塞避免 CPU 空耗 功能范围 仅支持单个变量/字段的原子操作 支持任意代码块的原子性,功能更全面 ABA 问题 存在,需额外机制解决 不存在,互斥访问避免中间状态变更
相关免费在线工具 Keycode 信息 查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
Escape 与 Native 编解码 JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
JavaScript / HTML 格式化 使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
JavaScript 压缩与混淆 Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
加密/解密文本 使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
Gemini 图片去水印 基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online