当前位置: 首页 > news >正文

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核心核心定论

  1. CAS是乐观锁、synchronized是悲观锁,二者并发设计理念完全对立

  2. CAS硬件级原子指令,执行过程不可被线程中断,天然保证操作原子性

  3. JDK5之后所有Atomic原子类、自旋锁、ConcurrentHashMap底层全部依托CAS实现

1.2 CAS完整执行过程

1.2.1 标准执行逻辑

  1. 线程从主内存读取共享变量,获取当前内存值V

  2. 线程传入预期修改旧值E、目标修改新值N

  3. CPU原子比对:内存值V == 预期旧值E?

    1. 相等:说明变量未被其他线程修改,直接将内存值V更新为新值NB,返回修改成功

    2. 不相等:说明变量已被其他线程篡改,放弃本次修改,返回修改失败

  4. 修改失败后,线程自选重试,直至修改成功

具体流程图如下:

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线上生产应用场景

  1. JDK并发容器底层:ConcurrentHashMap、CopyOnWriteArrayList读写CAS管控并发

  2. 原子计数场景:接口访问量、秒杀库存、全局序列号、线程计数器

  3. 自旋锁自定义实现:基于CAS实现轻量自旋锁,替代短时synchronized锁

  4. 乐观锁业务落地:数据库版本号乐观锁、分布式本地无锁扣减

  5. 线程池状态修改:线程池运行/关闭状态,CAS原子修改状态标识

  6. 自定义并发工具:实现限流计数器、令牌桶限流核心逻辑

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++源码流程

  1. Unsafe调用Jni接口,跳转hotspot源码unsafe.cpp

  2. 封装入参,调用Atomic::cmpxchg原子汇编方法

  3. 追加lock总线锁指令:锁定CPU总线,保证多CPU核心下指令原子性

  4. 执行CPU汇编指令:cmpxchg 完成硬件级比较交换

  5. 释放总线锁,返回布尔修改结果至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三大解决方案(优先级排序)

  1. 版本戳机制(最优方案):新增版本号Stamp,不仅比对变量值,同时比对版本号,每修改一次版本号自增,值相同版本不同则判定已修改,JDK自带AtomicStampedReference实现

  2. 时间戳标记:绑定变量最后修改时间,双重校验值+时间戳

  3. 业务全局唯一标识:业务层增加流水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全套原子类分类

  1. 基本数据类型原子类:AtomicInteger、AtomicLong、AtomicBoolean

  2. 引用类型原子类:AtomicReference、AtomicStampedReference、AtomicMarkableReference

  3. 数组原子类:AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray

  4. 对象字段原子类:AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater

  5. 累加器原子类(JDK8新增)::DoubleAccumulator、DoubleAdder、LongAccumulator、
    LongAdder、Striped64

  6. 复合标记原子类:带版本/标记防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原子类使用限制

  1. 多变量联动限制:无法同时原子修改多个无关共享变量,多变量必须使用锁机制

  2. 字段修饰限制:对象字段原子更新,目标字段必须被volatile修饰,否则无法保证可见性

  3. 访问权限限制:字段更新类,只能修改public/本类可访问字段,私有字段无法修改

  4. 功能场景限制:LongAdder仅适合计数,不适合精准条件修改业务

  5. 线程自旋限制:极端死锁竞争,CAS无限自旋,耗尽CPU核心资源

  6. 版本维护限制:防ABA原子类,业务需要手动维护版本号,编码复杂度提升

  7. 变量类型限制:只能是实例变量,只能是可修改变量

2.5 Atomic原子类线上生产分级应用场景

2.5.1 基础原子类场景(AtomicInteger/AtomicLong)

低并发接口计数、单点库存扣减、简单全局自增ID、状态标识修改

2.5.2 JDK8累加器场景(LongAdder)

超高并发流量统计、网关访问量、日志计数、监控指标累加、秒杀海量计数

2.5.3 防ABA引用原子类场景

资金账户余额修改、链表节点并发更新、分布式本地乐观锁、核心资产数据修改

2.5.4 对象字段原子更新场景

大量实体类状态修改,节省内存,无需封装整个对象为原子类,适配业务实体状态流转

2.5.5 数组原子类场景

并发批量下标数据统计、分片数据计数、数组点位独立修改业务

http://www.cnnetsun.cn/news/2963107.html

相关文章:

  • 终极OpenCore Legacy Patcher指南:让老旧Mac免费运行最新macOS的完整教程
  • Citra模拟器图形优化:从模糊到清晰的3DS游戏体验提升指南
  • MPC801指令缓存架构解析:两路组相联、LRU替换与锁定机制
  • Microchip 25AA256/25LC256 SPI EEPROM选型、硬件连接与软件驱动全解析
  • 终极指南:3种创新方法解决小爱音箱音乐服务DID配置难题
  • 如何快速掌握Adobe软件管理:完整开源工具使用指南
  • Microchip 24AA024H与24LC024H EEPROM选型指南:从电压、封装到实战应用
  • 2026 AI 学习平台评测:7 家机构对比 + 四类人群适配指南
  • 小红书2026.6.11推荐算法升级深度解析:语义质量评分、深度互动建模与AI内容检测的技术拆解
  • 高速ADC实战指南:从MCP37220/MCP37D20-200参数解读到系统设计避坑
  • 从物理引擎到数字孩生:构建奥运跳台滑雪比赛仿真系统
  • 25LC512 EEPROM选型、硬件设计与软件驱动实战指南
  • Effective C++ 条款53:不要轻忽编译器的警告
  • LLM与RNN混合模型在代码理解中的应用与优化
  • 24CS32 EEPROM硬件特性、I2C驱动与嵌入式存储实战指南
  • Cursor Pro账户管理终极指南:如何轻松绕过设备限制实现多账户自由切换
  • 2026年外贸工艺品市场趋势揭秘!知名资讯公司推荐排行来了
  • 小说下载终极指南:5分钟学会保存全网小说,告别404错误
  • shein列表页数据采集(验证码/加密)
  • MPC5200 USB主机控制器寄存器详解与DMA协同设计
  • PowerPC时间基寄存器深度解析:TB与TBREF实现纳秒级定时
  • 【收藏备用·2026版】数据人太难了!深耕大模型,解锁高薪逆袭之路
  • 3个简单方法快速解决小爱音箱音乐服务设备DID配置问题
  • 企业级AI员工应该具备哪些能力?为什么越来越多企业开始关注执行型AI
  • Mac Mouse Fix终极配置指南:从基础设置到专业级调优
  • 兰州汽车贴膜口碑排行榜:实测五家店,老司机都选这一家
  • 如何快速掌握Buck-Boost电感计算:面向初学者的实战指南
  • Discuz! X3.4安全攻防:从任意文件删除到完整Getshell攻击链深度剖析
  • PL2303驱动兼容性终极指南:轻松搞定Windows 10/11黄色感叹号问题
  • 本地运行Sulphur-2详细教程 亲测可行!