Java并发编程原理精讲:CAS与Atomic原子操作详解
一、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 数组原子类场景
并发批量下标数据统计、分片数据计数、数组点位独立修改业务
