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

Java并发——CAS(比较并替换)

在多线程编程中,如何安全地修改共享变量是永恒的课题。传统的synchronized关键字虽然保证了线程安全,但基于互斥锁的机制会导致线程阻塞、上下文切换,在竞争激烈的场景下可能成为性能瓶颈。于是,一种更轻量的同步方案——CAS(Compare And Swap)应运而生,它承载了无锁并发思想的落地实践。

本文将带你系统了解CAS的核心原理、底层实现、在Java中的应用以及必须面对的挑战,帮助你真正掌握这把并发编程的利器。

一、什么是CAS?

CAS的全称是Compare And Swap(比较并交换),它是一种CPU原语级的原子操作,用于实现多线程环境下共享变量的无锁修改。它包含三个核心参数:

  • V:主内存中的当前值(变量实际存储的位置)

  • A:线程预期的旧值(线程从主内存读取后保存在本地的副本)

  • B:线程希望写入的新值

CAS的执行过程极其简单:

如果 V == A,则将 V 更新为 B;否则不做任何操作。整个“比较+交换”过程由CPU硬件保证原子性,不会被任何其他线程中断。

从宏观上看,CAS是一条乐观的指令:它假定在读取之后到更新之前,没有其他线程修改过该变量;如果检测到被修改过,就失败重试。这种“失败则重试”的模式被称为自旋

二、为什么需要CAS?

考虑一个经典的计数器累加场景:

private int count = 0; public void increment() { count++; }

count++在字节码层面被拆分为“读取-修改-写回”三步,多线程并发执行时必然出现数据覆盖,最终结果小于预期。传统的解决方案是使用synchronized

public synchronized void increment() { count++; }

synchronized是悲观锁,无论是否有竞争都会加锁,线程获取锁失败时会进入阻塞状态,触发操作系统级别的上下文切换,开销较大。

CAS提供了一种乐观的替代方案:它假设大多数情况下没有冲突,只在更新时检查,失败则重试,避免了线程挂起和唤醒的开销。在竞争不激烈的场景下,CAS的性能远优于synchronized

三、CAS的底层实现

1. CPU层面的支持

CAS的原子性依赖于CPU提供的特殊指令。以x86架构为例,它提供了CMPXCHG指令,该指令可以完成“比较并交换”的操作。在多核环境下,该指令前会加上LOCK前缀,确保执行期间锁住总线或缓存行,防止其他核心同时访问该内存地址,从而保证原子性。

ARM架构则使用LDXR/STXR指令对,通过独占监视器实现类似的原子操作。

2. Java中的Unsafe类

在Java中,CAS操作由sun.misc.Unsafe类提供支持。该类是一个不安全的底层操作类,能够直接操作内存地址、执行CAS操作等。典型的CAS方法如下:

public final native boolean compareAndSwapInt(Object obj, long offset, int expect, int update);

其中offset是字段在对象中的内存偏移量,可以通过objectFieldOffset获取。普通开发者不应直接使用Unsafe,而是通过JDK提供的封装类——java.util.concurrent.atomic包下的原子类——来安全使用CAS。

3. AtomicInteger的源码解析

AtomicInteger.incrementAndGet()为例,我们看一下CAS的典型应用:

public final int incrementAndGet() { return unsafe.getAndAddInt(this, valueOffset, 1) + 1; }

getAndAddInt的实现是典型的自旋CAS:

public final int getAndAddInt(Object obj, long offset, int delta) { int current; do { current = this.getIntVolatile(obj, offset); // 获取当前值 } while (!this.compareAndSwapInt(obj, offset, current, current + delta)); return current; }

整个过程不断循环,直到CAS成功才退出。value字段被volatile修饰,保证了可见性,使得每次读取都能获取到最新的值。

四、CAS在Java中的应用

CAS是Java并发包(JUC)的基石,它的身影无处不在:

应用场景典型类说明
原子类AtomicIntegerAtomicLongAtomicReference通过CAS实现线程安全的单变量操作
高性能计数器LongAdder采用分段累加思想,将热点分散,减少CAS冲突
并发集合ConcurrentHashMap初始化桶、更新计数器时使用CAS
同步器ReentrantLockSemaphoreCountDownLatchAQS(AbstractQueuedSynchronizer)的state状态通过CAS维护

五、CAS的三大挑战与解决方案

任何技术都不是银弹,CAS也存在一些固有的缺陷,需要我们在使用时加以注意。

1. ABA问题

问题描述:一个变量的值从A变为B,又从B变回A。此时CAS检查时发现值仍为A,就会误认为没有被修改过,从而执行更新。在某些业务场景(如栈操作、银行转账)中,这种“中间过程”可能导致严重错误。

解决方案:引入版本号或时间戳。Java提供了AtomicStampedReference,它在比较时同时检查“值”和“版本号”,只有两者都匹配才更新。

AtomicStampedReference<String> ref = new AtomicStampedReference<>("A", 1); // 同时比较值和版本号 ref.compareAndSet("A", "B", 1, 2);

2. 循环时间长开销大(自旋消耗CPU)

问题描述:CAS通常配合自旋(无限循环)使用。在高并发下,大量线程同时争抢一个变量,频繁失败重试,会导致CPU占用率飙升。

解决方案

  • 限制自旋次数,超过一定次数后使用其他策略(如直接阻塞)。

  • 使用LongAdder等分段累加工具,将单点竞争分散到多个Cell上,减少冲突。

  • JVM自适应自旋(在锁升级中应用),根据历史成功率动态调整自旋次数。

3. 只能保证一个共享变量的原子操作

问题描述:CAS一次只能对一个内存地址进行原子操作,如果需要同时更新多个变量,则无法保证原子性。

解决方案

  • 将多个变量封装成一个对象,使用AtomicReference进行原子更新。

  • 使用传统的锁(synchronizedReentrantLock)保护临界区。

六、CAS vs synchronized:如何选择?

对比维度CASsynchronized
实现方式CPU原子指令(无锁)Monitor互斥锁
线程状态失败则自旋,不阻塞失败则阻塞,挂起唤醒开销大
适用场景低竞争、单变量简单操作高竞争、多变量复杂逻辑
可重入性不支持(需自行封装)支持
性能特点无竞争时极快,高竞争时自旋消耗CPU低竞争时一般,高竞争时性能下降但可控

选择建议

  • 对于简单的计数器、状态标记,优先使用AtomicIntegerLongAdder等。

  • 对于复杂的临界区、多变量操作,或需要wait/notify通信时,使用synchronizedReentrantLock

  • 实际开发中,通常优先使用JUC提供的高级工具,避免手写CAS逻辑。

七、总结

CAS作为并发编程中的一项关键技术,用无锁的方式实现了线程安全的变量更新,极大地提升了系统在低竞争场景下的性能。它的实现依赖于CPU指令和JVM底层的Unsafe类,为Java提供了强大的无锁并发能力。

但同时,我们也要清醒地认识到CAS并非万能——ABA问题、自旋开销和单变量限制是它的三大软肋。只有在合适的场景下合理使用,才能扬长避短,写出高效、可靠的并发代码。

synchronized到CAS,再到LongAdderCompletableFuture,Java并发的演化史正是一部不断追求性能和易用性的历史。理解CAS,就是理解了现代Java并发编程的基石

http://www.jsqmd.com/news/522809/

相关文章:

  • 避坑指南:Kscan暴力破解模块的正确打开方式(含自定义字典配置)
  • 告别‘幽灵机械臂’:在Ubuntu 20.04 + ROS Noetic下,用Xacro重构你的SolidWorks URDF模型
  • Qwen3-Reranker-0.6B惊艳效果:重排序使RAG在复杂嵌套Query中准确率翻倍
  • 自动化测试实践:为cv_unet_image-colorization模型服务编写全面的测试用例
  • 声音克隆黑科技!用Fish Speech 1.5上传5秒音频,克隆你的专属语音
  • 2026 年金三银四版互联网大厂 Java 面试指南
  • 基于COM接口的MATLAB与Origin自动化数据管道构建
  • 279商业模式纯解析:老板一眼看透的底层逻辑
  • 用PPO算法搞定机器人仿真参数调优:从零到一的Isaac Gym实战指南
  • 嵌入式工程师七阶能力跃迁模型与工程验证体系
  • 智能体开发避坑指南:CoreAgent平台搭建企业级AI员工的5个关键配置
  • 实战指南:主流图像篡改检测数据集深度解析与应用
  • nginx-module-vts未来展望:新特性路线图与社区发展动态
  • Gemma-3-12b-it多模态效果集:卫星地图截图→地理要素识别→区域分析报告
  • yz-bijini-cosplay开源镜像部署:RTX 4090专属LoRA+Z-Image底座一键运行
  • Nunchaku-flux-1-dev多场景落地手册:教育课件插图、文旅宣传海报、非遗数字藏品生成
  • FRCRN语音降噪工具保姆级教程:Windows PowerShell自动化预处理流程
  • RK3588 NPU加速:从零构建边缘端人脸识别系统
  • 5大功能彻底解决BIM模型处理难题:IfcOpenShell开源BIM工具实战指南
  • Z-Image-Turbo-rinaiqiao-huiyewunv 跨平台部署:在WSL2中配置开发与测试环境
  • 避坑指南:Windows下用llama.cpp部署DeepSeek量化模型遇到的7个典型报错
  • 西门子200SMART PLC与西门子V20变频器Modbus轮询通讯及触摸屏源程序详解
  • SPSS岭回归保姆级教程:从语法调用到结果解读,手把手教你搞定多重共线性
  • 保姆级教程:在Ubuntu 18.04上搞定Intel D455相机驱动与ROS(Melodic)环境,告别报错
  • AD09实战指南:高效生成BOM表的技巧与优化
  • 从零搭建Vue3官网项目:用Vite4+PostCSS实现一套代码适配所有设备
  • Eureka 在大数据存储中的应用探索
  • MAX44009环境光传感器驱动开发与STM32/FreeRTOS工程实践
  • 手把手用Python处理Lanelet2地图数据:从Point到Regulatory Element的完整操作指南
  • AI智能证件照制作工坊如何防止滥用?API限流机制设计