一、CAS无锁并发原理全解
1.1 CAS介绍
1.1.1 CAS全称与定义
CAS全称Compare And Swap,比较并交换 ,是非阻塞同步的实现原理,是CPU硬件级别提供的无锁原子指令,属于乐观锁核心实现原理,JDK底层依托Unsafe类封装调用,实现多线程下变量无锁安全修改。
CAS三大核心操作参数:内存值V、预期旧值E、更新新值N
1.1.2 CAS核心核心定论
-
CAS是乐观锁、synchronized是悲观锁,二者并发设计理念完全对立
-
CAS硬件级原子指令,执行过程不可被线程中断,天然保证操作原子性
-
JDK5之后所有Atomic原子类、自旋锁、ConcurrentHashMap底层全部依托CAS实现
1.2 CAS完整执行过程
1.2.1 标准执行逻辑
-
线程从主内存读取共享变量,获取当前内存值V
-
线程传入预期修改旧值E、目标修改新值N
-
CPU原子比对:内存值V == 预期旧值E?
-
相等:说明变量未被其他线程修改,直接将内存值V更新为新值NB,返回修改成功
-
不相等:说明变量已被其他线程篡改,放弃本次修改,返回修改失败
-
-
修改失败后,线程自选重试,直至修改成功
具体流程图如下:

1.2.2 通俗案例
银行卡余额:当前余额100(V),我预期余额100(E),想要充值改为200(N);查询余额没变则修改,余额变动则放弃充值,重新读取余额重试。
1.3 CAS代码使用方式
Java无法直接调用CPU指令,必须通过sun.misc.Unsafe底层类调用native本地方法执行CAS操作,分为原生Unsafe调用、原子类封装调用两种方式。
1.3.1 方式1:原生Unsafe手动CAS实操
public class CasUnsafeDemo {
// 共享变量
private volatile int num = 0;
// 获取Unsafe底层对象
private static final Unsafe UNSAFE;
// 变量内存偏移量
private static long offset;
static {
try {
UNSAFE = Unsafe.getUnsafe();
// 获取num变量内存地址偏移量
offset = UNSAFE.objectFieldOffset(CasUnsafeDemo.class.getDeclaredField("num"));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
CasUnsafeDemo demo = new CasUnsafeDemo();
// CAS参数:对象、偏移量、预期旧值、更新新值
boolean success = UNSAFE.compareAndSwapInt(demo, offset, 0, 100);
System.out.println("CAS修改结果:"+success); // true
}
}
以compareAndSwapInt 为例,Unsafe 的compareAndSwapInt 方法接收4 个参数,分别是:对象实例、内存偏移量、字段期望值、字段新值。该方法会针对指定对象实例中的相应偏移量的字段执行CAS 操作。
1.3.2 方式2:AtomicInteger封装简化使用(生产常用)
// 内置封装CAS,无需手动操作Unsafe偏移量
AtomicInteger atomicInteger = new AtomicInteger(0);
// 比较并交换:预期0,更新为200
boolean casResult = atomicInteger.compareAndSet(0,200);
1.4 CAS线上生产应用场景
-
JDK并发容器底层:ConcurrentHashMap、CopyOnWriteArrayList读写CAS管控并发
-
原子计数场景:接口访问量、秒杀库存、全局序列号、线程计数器
-
自旋锁自定义实现:基于CAS实现轻量自旋锁,替代短时synchronized锁
-
乐观锁业务落地:数据库版本号乐观锁、分布式本地无锁扣减
-
线程池状态修改:线程池运行/关闭状态,CAS原子修改状态标识
-
自定义并发工具:实现限流计数器、令牌桶限流核心逻辑
1.5 CAS底层源码深度分析(JDK8 HotSpot)
1.5.1 Java层Unsafe入口源码
// native本地方法,直接对接操作系统+CPU指令
public final native boolean compareAndSwapInt(Object o, long offset,int expected,int x);
1.5.2 JNI本地C++源码流程
-
Unsafe调用Jni接口,跳转hotspot源码unsafe.cpp
-
封装入参,调用Atomic::cmpxchg原子汇编方法
-
追加lock总线锁指令:锁定CPU总线,保证多CPU核心下指令原子性
-
执行CPU汇编指令:cmpxchg 完成硬件级比较交换
-
释放总线锁,返回布尔修改结果至Java层
1.5.3 核心底层要点
-
volatile配合CAS:volatile保证变量内存可见性,CAS保证修改原子性,二者缺一不可
-
lock指令:锁定总线,禁止其他CPU同时修改共享变量,解决多核并发竞争
1.6 CAS核心优势(对比synchronized悲观锁)
-
无线程阻塞:CAS自旋重试,不会进入内核态阻塞线程,无线程上下文切换开销
-
性能极高:短时竞争场景,CAS吞吐量远超重量级synchronized锁
-
开销极低:用户态完成操作,无需内核态切换,系统资源占用少
-
粒度灵活:仅针对单个共享变量修改,锁粒度远小于对象锁、类锁
-
代码轻量化:原子类封装后编码简单,无需手动加锁解锁
1.7 CAS原生四大缺陷
-
自旋开销大:高并发竞争激烈时,CAS无限重试自旋,持续占用CPU资源,CPU飙高
-
只能保证单个变量原子性:无法实现多个共享变量联合原子修改,仅支持单变量操作
-
无法拦截业务逻辑:仅能比对变量值,无法管控业务代码流程
-
存在ABA并发问题:变量值轮回篡改,CAS无法感知中间修改流程,判定修改无误,引发业务bug
1.8 ABA问题完整闭环:定义、危害、解决方案
1.8.1 ABA问题标准定义
线程1读取内存值A,准备CAS修改;期间线程2将值A改为B,再改回A;线程1比对内存值依旧为A,判定变量未修改,直接执行修改,忽略变量中间篡改流程,即为ABA问题。
1.8.2 ABA业务危害
资金转账、库存扣减、链表节点修改、分布式余额场景,会出现数据错乱、节点丢失、资金扣减异常,高资产业务致命bug。
1.8.3 ABA三大解决方案(优先级排序)
-
版本戳机制(最优方案):新增版本号Stamp,不仅比对变量值,同时比对版本号,每修改一次版本号自增,值相同版本不同则判定已修改,JDK自带AtomicStampedReference实现
-
时间戳标记:绑定变量最后修改时间,双重校验值+时间戳
-
业务全局唯一标识:业务层增加流水ID,规避数值轮回复用
1.8.4 解决ABA代码示例
// 带版本戳原子引用,解决ABA
AtomicStampedReference<Integer> stamped = new AtomicStampedReference<>(100,1);
// 参数:预期值、新值、预期版本、新版本
stamped.compareAndSet(100,200,1,2);
二、Atomic原子操作类全解(JDK8专属差异化)
在并发编程中很容易出现并发安全的问题,有一个很简单的例子就是多线程更新变量i=1,比如多个
线程执行i++操作,就有可能获取不到正确的值,而这个问题,最常用的方法是通过Synchronized进行控制来达到线程安全的目的。但是由于synchronized是采用的是悲观锁策略,并不是特别高效的一种解决方案。实际上,在J.U.C下的atomic包提供了一系列的操作简单,性能高效,并能保证线程安全的类去更新基本类型变量,数组元素,引用类型以及更新对象中的字段类型。atomic包下的这些类都是采用的是乐观锁策略去原子更新数据,在java中则是使用CAS操作具体实现。
2.1 JDK8六大分类原子操作类、JDK7/JDK8差异对比
2.1.1 JDK8全套原子类分类
-
基本数据类型原子类:AtomicInteger、AtomicLong、AtomicBoolean
-
引用类型原子类:AtomicReference、AtomicStampedReference、AtomicMarkableReference
-
数组原子类:AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray
-
对象字段原子类:AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater
-
累加器原子类(JDK8新增)::DoubleAccumulator、DoubleAdder、LongAccumulator、
LongAdder、Striped64
-
复合标记原子类:带版本/标记防ABA专属原子类
2.1.2 JDK7与JDK8原子类核心差异
|---------|-----------------------|--------------------------------|
| 对比维度 | JDK7原子类 | JDK8原子类 |
| 底层CAS策略 | 单值无限自旋CAS,所有线程竞争同一个变量 | 分段Cell数组分散竞争,多线程分片CAS |
| 高并发性能 | 竞争激烈自旋耗时久,CPU占用极高 | 分散竞争,自旋次数大幅降低,性能提升5-10倍 |
| 新增类 | 无累加器工具类 | 新增LongAdder、Accumulator函数式累加类 |
| 内存布局 | 无缓存行填充,伪共享问题严重 | @sun.misc.Contended缓存行填充,解决伪共享 |
2.2 全类型原子类实操示例代码(可直接运行)
2.2.1 基本类型原子类示例
// 整型原子类
AtomicInteger atomicInt = new AtomicInteger(0);
atomicInt.getAndIncrement(); // i++
atomicInt.incrementAndGet(); // ++i
atomicInt.addAndGet(5); // 累加指定值
atomicInt.compareAndSet(5,10); // CAS修改
2.2.2 引用类型防ABA原子类示例
// 带版本戳,彻底解决ABA
User user1 = new User(1,"张三");
User user2 = new User(2,"李四");
// 初始值+初始版本号
AtomicStampedReference<User> atomicUser = new AtomicStampedReference<>(user1,1);
// 校验值+版本,双重CAS
atomicUser.compareAndSet(user1,user2,1,2);
2.2.3 数组原子类示例
// 数组下标元素原子修改,线程安全
AtomicIntegerArray intArray = new AtomicIntegerArray(new int[]{1,2,3});
// 修改下标0元素,预期1改为99
intArray.compareAndSet(0,1,99);
2.2.4 对象字段更新原子类示例
// 仅修改对象volatile字段,无需封装整个对象,节省内存
class Student{
public volatile int age;
}
// 指定类+字段原子更新
AtomicIntegerFieldUpdater<Student> updater = AtomicIntegerFieldUpdater.newUpdater(Student.class,"age");
Student student = new Student();
updater.compareAndSet(student,0,18);
2.2.5 JDK8新增LongAdder分段累加示例
// 高并发计数首选,分段累加,性能碾压AtomicLong
LongAdder longAdder = new LongAdder();
longAdder.increment();
longAdder.add(10);
// 汇总所有分段数值,获取总数
long total = longAdder.sum();
2.3 Atomic原子操作类整体优缺点
2.3.1 通用优点
-
底层CAS无锁,并发性能优于synchronized重量级锁
-
API封装完善,无需手动操作Unsafe、内存偏移量,开发便捷
-
细分场景适配齐全:基础类型、数组、对象、防ABA全覆盖
-
JDK8优化分段CAS、缓存行填充,解决高并发自旋、伪共享痛点
-
轻量无阻塞,适合短时高频变量修改场景
2.3.2 通用缺点
-
基础原子类存在ABA问题,业务需额外引入版本戳原子类
-
超高并发竞争下,依旧存在自旋空转、CPU占用升高问题
-
仅支持变量原子修改,无法实现多行业务代码原子性
-
LongAdder只能做累加统计,无法精准CAS修改指定数值
2.4 Atomic原子类使用限制
-
多变量联动限制:无法同时原子修改多个无关共享变量,多变量必须使用锁机制
-
字段修饰限制:对象字段原子更新,目标字段必须被volatile修饰,否则无法保证可见性
-
访问权限限制:字段更新类,只能修改public/本类可访问字段,私有字段无法修改
-
功能场景限制:LongAdder仅适合计数,不适合精准条件修改业务
-
线程自旋限制:极端死锁竞争,CAS无限自旋,耗尽CPU核心资源
-
版本维护限制:防ABA原子类,业务需要手动维护版本号,编码复杂度提升
-
变量类型限制:只能是实例变量,只能是可修改变量
2.5 Atomic原子类线上生产分级应用场景
2.5.1 基础原子类场景(AtomicInteger/AtomicLong)
低并发接口计数、单点库存扣减、简单全局自增ID、状态标识修改
2.5.2 JDK8累加器场景(LongAdder)
超高并发流量统计、网关访问量、日志计数、监控指标累加、秒杀海量计数
2.5.3 防ABA引用原子类场景
资金账户余额修改、链表节点并发更新、分布式本地乐观锁、核心资产数据修改
2.5.4 对象字段原子更新场景
大量实体类状态修改,节省内存,无需封装整个对象为原子类,适配业务实体状态流转
2.5.5 数组原子类场景
并发批量下标数据统计、分片数据计数、数组点位独立修改业务