Java CAS 1. 概念及基本原理CAS compare and swap 的缩写中文翻译成 比较并交换 , 是实现并发算法时常用到的一种技术。它包含三个操作数——内存位置、预期原值 及 更新值。执行 CAS 操作的时候将内存位置的值与预期原值比较如果 相匹配那么处理器会自动将该位置值更新为新值如果 不匹配处理器不做任何操作多个线程同时执行 CAS 操作只有一个会成功。原理CAS有3个操作数位置内存值V旧的预期值A要修改的更新值B。当且仅当旧的预期值A和内存值V相同时将内存值V修改为B否则什么都不做或重来CAS 是 JDK 提供的非阻塞 原子性操作它通过硬件保证了比较-更新的原子性。它是非阻塞的且自身原子性也就是说这玩意效率更高且通过硬件保证说明这玩意更可靠。CAS 是一条 CPU 的原子指令cmpxchg指令不会造成所谓的数据不一致问题Java 的 Unsafe 提供的 CAS方法如 compareAndSwapXXX底层实现即为 CPU 指令 cmpxchg。执行 cmpxchg 指令的时候会判断当前系统是否为多核系统如果是就给总线加锁只有一个线程会对总线加锁成功加锁成功之后会执行cas操作也就是说 CAS 的原子性实际上是 CPU 实现的 其实在这一点上还是有排他锁的只是比起用 synchronized 这里的排他时间要短的多 所以在多线程情况下性能会比较好Demo:publicstaticvoidmain(String[]args)throwsInterruptedException{AtomicIntegeratomicIntegernewAtomicInteger(5);System.out.println(atomicInteger.compareAndSet(5,2020)\tatomicInteger.get());System.out.println(atomicInteger.compareAndSet(5,1024)\tatomicInteger.get());}2. CAS 底层原理2.1 UnSafecompareAndSet() 方法的源代码上面三个方法都是类似的主要对4个参数做一下说明。var1表示要操作的对象var2表示要操作对象中属性地址的偏移量var4表示需要修改数据的期望的值var5/var6表示需要修改为的新值publicfinalbooleancompareAndSet(intexpectedValue,intnewValue){returnU.compareAndSetInt(this,VALUE,expectedValue,newValue);}其底层是 UnSafe 类的compareAndSet()方法。1 Unsafe是 CAS 的核心类由于 Java 方法无法直接访问底层系统需要通过本地native方法来访问Unsafe 相当于一个后门基于该类可以直接操作特定内存的数据。Unsafe 类存在于 sun.misc 包中其内部方法操作可以像 C 的指针一样直接操作内存因为 Java 中 CAS 操作的执行依赖于 Unsafe 类的方法。注意 Unsafe 类中的所有方法都是 native 修饰的也就是说 Unsafe 类中的方法都直接调用操作系统底层资源执行相应任务。2 变量 valueOffset表示该变量值在内存中的偏移地址因为 Unsafe 就是根据内存偏移地址获取数据的。3 变量 value 用 volatile 修饰保证了多线程之间的内存可见性。2.2 atomicInteger.getAndIncrement(); 如何保证线程安全的我们知道 i 是线程不安全的 那么atomicInteger.getAndIncrement();是如何保证线程安全的呢CAS 是一条 CPU并发原语。它的功能是判断内存某个位置的值是否为预期值如果是则更改为新的值这个过程是原子的。AtomicInteger类主要利用 CAS (compare and swap) volatile 和 native 方法来保证原子操作从而避免 synchronized 的高开销执行效率大为提升。CAS 并发原语体现在 JAVA 语言中就是 sun.misc.Unsafe 类中的各个方法。调用 UnSafe 类中的 CAS 方法JVM 会帮我们实现出 CAS 汇编指令。这是一种完全依赖于硬件的功能通过它实现了原子操作。再次强调由于 CAS 是一种系统原语原语属于操作系统用语范畴是由若干条指令组成的用于完成某个功能的一个过程并且原语的执行必须是连续的在执行过程中不允许被中断也就是说 CAS 是一条 CPU的原子指令不会造成所谓的数据不一致问题。2.3 源码分析newAtomicInteger(10);atomicInteger.getAndIncrement();OpenJDK源码里面查看下Unsafe.java假设线程A 和 线程B 两个线程同时执行 getAndAddInt 操作分别跑在不同CPU上1 AtomicInteger 里面的 value 原始值为3即主内存中 AtomicInteger 的v alue 为3根据 JMM 模型线程A 和 线程B 各自持有一份值为 3 的 value 的副本分别到各自的工作内存。2 线程A 通过 getIntVolatile(var1, var2) 拿到 value 值3这时 线程A 被挂起。3 线程B 也通过 getIntVolatile(var1, var2) 方法获取到 value 值3此时刚好线程B没有被挂起并执行compareAndSwapInt 方法比较内存值也为3成功修改内存值为4线程B打完收工一切OK。4 这时线程A恢复执行 compareAndSwapInt 方法比较发现自己手里的值数字3和主内存的值数字4不一致说明该值已经被其它线程抢先一步修改过了那A线程本次修改失败只能重新读取重新来一遍了。5 线程A重新获取value值因为变量value被volatile修饰所以其它线程对它的修改线程A总是能够看到线程A继续执行 compareAndSwapInt 进行比较替换直到成功。3. 原子引用classUser{StringuserName;intage;}publicclassAtomicReferenceDemo{publicstaticvoidmain(String[]args){Userz3newUser(z3,24);Userli4newUser(li4,26);AtomicReferenceUserarunewAtomicReference();atomicReferenceUser.set(z3);System.out.println(aru.compareAndSet(z3,li4)\taru.get().toString());System.out.println(aru.compareAndSet(z3,li4)\taru.get().toString());}}4. 自旋锁 借鉴CAS思想自旋锁spinlock是指尝试获取锁的线程不会立即阻塞而是采用循环的方式去尝试获取锁当线程发现锁被占用时会不断循环判断锁的状态直到获取。这样的好处是减少线程上下文切换的消耗缺点是循环会消耗CPU.下图是 OpenJDK 源码里面查看下 Unsafe.java5. CAS 缺点5.1 循环时间长 开销很大我们可以看到getAndAddInt方法执行时有个do while如果 CAS 失败会一直进行尝试。如果 CAS 长时间一直不成功可能会给 CPU 带来很大的开销。5.2 ABA 问题CAS会导致“ABA问题”。CAS 算法实现一个重要前提需要取出内存中某时刻的数据并在当下时刻比较并替换那么在这个时间差类会导致数据的变化。比如说一个线程 one 从内存位置V中取出A这时候另一个线程two也从内存中取出A并且线程two进行了一些操作将值变成了B然后线程 two又将V位置的数据变成A这时候线程one进行CAS操作发现内存中仍然是A然后线程one操作成功。尽管线程one的CAS操作成功但是不代表这个过程就是没有问题的。解决ABA问题AtomicStampedReferencepublicclassABADemo{staticAtomicIntegeratomicIntegernewAtomicInteger(100);staticAtomicStampedReferenceatomicStampedReferencenewAtomicStampedReference(100,1);publicstaticvoidmain(String[]args){newThread(()-{atomicInteger.compareAndSet(100,101);atomicInteger.compareAndSet(101,100);},t1).start();newThread(()-{//暂停一会儿线程try{Thread.sleep(500);}catch(InterruptedExceptione){e.printStackTrace();};System.out.println(atomicInteger.compareAndSet(100,2019)\tatomicInteger.get());},t2).start();//暂停一会儿线程,main彻底等待上面的ABA出现演示完成。try{Thread.sleep(2000);}catch(InterruptedExceptione){e.printStackTrace();}System.out.println(以下是ABA问题的解决);newThread(()-{intstampatomicStampedReference.getStamp();System.out.println(Thread.currentThread().getName()\t 首次版本号:stamp);//1//暂停一会儿线程,try{Thread.sleep(1000);}catch(InterruptedExceptione){e.printStackTrace();}atomicStampedReference.compareAndSet(100,101,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()1);System.out.println(Thread.currentThread().getName()\t 2次版本号:atomicStampedReference.getStamp());atomicStampedReference.compareAndSet(101,100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()1);System.out.println(Thread.currentThread().getName()\t 3次版本号:atomicStampedReference.getStamp());},t3).start();newThread(()-{intstampatomicStampedReference.getStamp();System.out.println(Thread.currentThread().getName()\t 首次版本号:stamp);//1//暂停一会儿线程获得初始值100和初始版本号1故意暂停3秒钟让t3线程完成一次ABA操作产生问题try{Thread.sleep(3000);}catch(InterruptedExceptione){e.printStackTrace();}booleanresultatomicStampedReference.compareAndSet(100,2019,stamp,stamp1);System.out.println(Thread.currentThread().getName()\tresult\tatomicStampedReference.getReference());},t4).start();}}

相关新闻

最新新闻

PSSE中IEEE 14节点系统建模与Python自动化稳定性仿真

PSSE中IEEE 14节点系统建模与Python自动化稳定性仿真

简介:本资源面向电力系统专业本科生、研究生及仿真分析初学者,提供PSSE与Python协同开展IEEE 14节点系统稳定性仿真的完整实践范例,解决传统手动操作效率低、结果处理繁琐等实际问题。压缩包共17个文件,涵盖3个核心Python脚本&…

2026/8/31 13:35:11
垃圾分类运输路径优化:从VRP建模到遗传算法实战

垃圾分类运输路径优化:从VRP建模到遗传算法实战

简介:本资源面向2025年电工杯数学建模竞赛参赛队伍及建模学习者,聚焦B题‘城市垃圾分类运输的路径优化与调度’这一现实痛点问题,提供从解题思路、模型构建到结果落地的全流程解决方案。压缩包共含多类核心文件,包括Word格式无水印…

2026/8/31 13:35:11
FFmpeg实战:跑团熟肉字幕抽取与批量转码处理指南

FFmpeg实战:跑团熟肉字幕抽取与批量转码处理指南

这次我们来看一个跑团熟肉物料处理过程中非常典型的场景:拿到一个“【coc跑团熟肉】神话与科学 part2 馒馒来克苏鲁神话”这类长视频资源,想要做本地归档、字幕提取、片段剪裁,结果发现视频能放、字幕却对不上,甚至转码时直接报错…

2026/8/31 13:35:11
ICON Decomposition:多变量概念分解如何支撑深度模型审计

ICON Decomposition:多变量概念分解如何支撑深度模型审计

模型上线前,我通常会把一批“高风险样本”拿出来单独过一遍,看看模型到底在看什么。早期大家比较熟悉的是热力图,比如 Grad-CAM 之类,能告诉 model 的注意力落在哪里。但如果只做这一步,你会发现很多问题说不清楚&…

2026/8/31 13:35:11
Ubuntu装完别急着换壁纸:换源、输入法、显卡驱动与Docker配置指南

Ubuntu装完别急着换壁纸:换源、输入法、显卡驱动与Docker配置指南

很多人的 Ubuntu 之旅是从换壁纸开始的。装好系统,打开浏览器,翻遍壁纸网站,选一张 4K 极简风景图,然后装上 GNOME Tweaks,把 Dock 调成透明,给终端换个主题,再配一套图标包。做完这些&#xff…

2026/8/31 13:35:11
ai-memory用Copilot和OpenAI OAuth:零API Key配置订阅账号指南

ai-memory用Copilot和OpenAI OAuth:零API Key配置订阅账号指南

ai-memory用Copilot和OpenAI OAuth:零API Key配置订阅账号指南 【免费下载链接】ai-memory Solution for long term memory for agent coding CLIs and to facilitate handoff between different agent vendors 项目地址: https://gitcode.com/GitHub_Trending/ai…

2026/8/31 13:30:11