一、基本概念
CAS是一种原子操作,用于实现无锁并发编程,是乐观锁的实现方式之一。
它包含三个操作数:
内存位置 V(需要读写的变量)
期望值 A(线程认为 V 当前应该有的值)
新值 B(想要写入的新值)
核心逻辑:
if (V的值 == A) { V的值 = B; 返回成功; } else { 返回失败; }关键点:比较和交换是一个原子操作,由硬件指令保证(如x86的CMPXCHG)
二、为什么需要CAS?
传统锁的问题:
线程阻塞/唤醒有上下文切换开销
可能产生死锁、优先级反转等问题
CAS的优势:
✅ 无阻塞,不会挂起线程
✅ 无锁开销,性能更高
✅ 避免死锁
| 对比维度 | CAS(无锁) | 锁 |
|---|---|---|
| 线程阻塞 | 不阻塞 | 阻塞 |
| 上下文切换 | 无 | 有 |
| 适用场景 | 低/中竞争,操作短 | 高竞争,操作长 |
| 风险 | ABA、活锁、CPU空转 | 死锁、优先级反转 |
| 性能 | 竞争小时极快 | 竞争大时稳定 |
三、CAS的三大问题及解决方案
问题1:ABA问题
场景:值从 A → B → A,CAS误认为没变化
解决方案:增加版本号/时间戳
// 带版本号的引用 AtomicStampedReference<String> ref = new AtomicStampedReference<>("A", 0); int[] stamp = new int[1]; String value = ref.get(stamp); // 获取值和版本号 // 更新时必须同时匹配值和版本号 boolean success = ref.compareAndSet( "A", "B", // 期望值,新值 stamp[0], stamp[0] + 1 // 期望版本,新版本 );问题2:循环时间长开销大
现象:高竞争下CAS失败,线程一直自旋消耗CPU
解决方案:
限制自旋次数
使用
LongAdder(分段累加,最后汇总)退避策略(延迟重试)
问题3:只能保证一个共享变量的原子操作
解决方案:
将多个变量封装成对象,用
AtomicReference使用锁(
synchronized/ReentrantLock)
四、Atomic原子操作类
java.util.concurrent.atomic包下的原子类,基于CAS实现的无锁、线程安全的操作类。
Atomic类分类
1. 基本类型
| 类名 | 说明 |
|---|---|
AtomicInteger | 原子整型 |
AtomicLong | 原子长整型 |
AtomicBoolean | 原子布尔型 |
2. 数组类型
| 类名 | 说明 |
|---|---|
AtomicIntegerArray | 原子整型数组 |
AtomicLongArray | 原子长整型数组 |
AtomicReferenceArray | 原子引用数组 |
3. 引用类型
| 类名 | 说明 |
|---|---|
AtomicReference<V> | 原子引用 |
AtomicStampedReference<V> | 带版本号的引用(解决ABA) |
AtomicMarkableReference<V> | 带标记位的引用 |
4. 字段更新器
| 类名 | 说明 |
|---|---|
AtomicIntegerFieldUpdater | 原子更新整型字段 |
AtomicLongFieldUpdater | 原子更新长整型字段 |
AtomicReferenceFieldUpdater | 原子更新引用字段 |
5. 累加器(JDK 1.8+)
| 类名 | 说明 |
|---|---|
LongAdder | 高性能长整型累加器 |
DoubleAdder | 高性能双精度累加器 |
LongAccumulator | 自定义累加逻辑 |
DoubleAccumulator | 自定义累加逻辑 |
核心要点:
Atomic类基于CAS + volatile实现无锁并发
避免锁竞争,适合低/中并发场景
高竞争累加器用
LongAdder性能更好需要解决ABA问题用
AtomicStampedReference注意原子组合操作的危险