什么是CAS机制?
CAS和Synchronized的区别是什么?适合什么样的场景?有什么样的优点和缺点?
示例程序:启动两个线程,每个线程中让静态变量count循环累加100次。
public class ThreadTest {private static int count = 0;public static void main(String[] args) {for (int i = 0; i < 2; i++) {//开启两个线程new Thread(new Runnable() {@Overridepublic void run() {try {Thread.sleep(10);} catch (Exception e) {e.printStackTrace();}//每个线程自增100for (int i = 0; i < 100; i++) {count++;}}}).start();}try {Thread.sleep(200);} catch (Exception e) {e.printStackTrace();}System.out.println("count="+count);}
}
最终输出的count结果是什么呢?一定会是200吗?
因为这段代码不是线程安全的,所以最终的自增结果很可能少于200!
加上Synchronized同步锁,看看结果:
public class ThreadTest {private static int count = 0;public static void main(String[] args) {for (int i = 0; i < 2; i++) {//开启两个线程new Thread(new Runnable() {@Overridepublic void run() {try {Thread.sleep(10);} catch (Exception e) {e.printStackTrace();}//每个线程自增100for (int i = 0; i < 100; i++) {//加上同步锁synchronized (ThreadTest.class) {count++;}}}}).start();}try {Thread.sleep(200);} catch (Exception e) {e.printStackTrace();}System.out.println("count="+count);}
}
加了同步锁之后,count自增的操作变成了原子性操作,所以最终的输出一定是count=200,代码实现了线程安全。
Synchronized的确保证了线程安全,但是在某些情况下,确不是最优选择。
为什么这么说呢?关键在于性能问题。
Synchronized关键字会让没有得到锁资源的线程进入BLOCKED状态,而后在争夺到锁资源后恢复为RUNNABLE状态,这个过程中涉及到操作系统用户模式和内核模式的转换,代价比较高。
尽管Java1.6为Synchronized做了优化,增加了从偏向锁到轻量级锁再到重量级锁的过度,但是在最终转变为重量级锁之后**,性能仍然较**低。
还有别的方法吗?
有没有听说过,java当中的原子操作类?
所谓原子操作类,指的是java.util.concurrent.atomic包下,一系列以Atomic开头的包装类。例如AtomicBoolean,AtomicInteger,AtomicLong。它们分别用于Boolean,Integer,Long类型的原子性操作。
现在我们尝试在代码中引入AtomicInteger类:
public class ThreadTest {
// private static int count = 0;private static AtomicInteger count = new AtomicInteger(0);public static void main(String[] args) {for (int i = 0; i < 2; i++) {//开启两个线程new Thread(new Runnable() {@Overridepublic void run() {try {Thread.sleep(10);} catch (Exception e) {e.printStackTrace();}//每个线程自增100for (int i = 0; i < 100; i++) {//加上同步锁
// synchronized (ThreadTest.class) {
// count++;
// }count.incrementAndGet();}}}).start();}try {Thread.sleep(200);} catch (Exception e) {e.printStackTrace();}System.out.println("count="+count);}
}
使用AtomicInteger之后,最终的输出结果同样可以保证是200。并且在某些情况下,代码的性能会比Synchronized更好。
Atomic操作类的底层,正是用了CAS机制。
什么是CAS?
CAS是英文单词Compare And Swap的缩写,翻译过来就是比较并替换。
CAS机制当中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B。
更新一个变量的时候,只有当变量的预期值A和内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为B。
这样说或许有些抽象,我们来看一个例子:
- 在内存地址V当中,存储着值为10的变量。

- 此时线程1想要把变量的值增加1。对线程1来说,旧的预期值A=10,要修改的新值B=11。

- 在线程1要提交更新之前,另一个线程2抢先一步,把内存地址V中的变量值率先更新成了11。

- 线程1开始提交更新,首先进行A和地址V的实际值比较(Compare),发现A不等于V的实际值,提交失败。

- 线程1重新获取内存地址V的当前值,并重新计算想要修改的新值。此时对线程1来说,A=11,B=12。这个重新尝试的过程被称为自旋。

- 这一次比较幸运,没有其他线程改变地址V的值。线程1进行Compare,发现A和地址V的实际值是相等的。

- 线程1进行SWAP,把地址V的值替换为B,也就是12。

从思想上来说,Synchronized属于悲观锁,悲观地认为程序中的并发情况严重,所以严防死守。CAS属于乐观锁,乐观地认为程序中的并发情况不那么严重,所以让线程不断去尝试更新。
这两种机制没有绝对的好与坏,关键看使用场景。在并发量非常高的情况下,反而用同步锁更合适一些。
Java当中都有哪些地方应用到了CAS机制呢?
- Atomic系统
- Lock系列类的底层实现
甚至在java1.6以上版本,Synchronized转变为重量级锁之前,也会采用CAS机制。
CAS机制有哪些缺点?
- CPU开销较大
在并发量比较高的情况下,如果许多线程反复尝试更新某一个变量,却又一直更新不成功,循环往复,会给CPU带来很大的压力。
- 不能保证代码块的原子性
CAS机制所保证的只是一个变量的原子性操作,而不能保证整个代码块的原子性。比如需要保证3个变量共同进行原子性的更新,就不得不使用Synchronized了。
- ABA问题
两个问题如下需要解决:
- Java当中CAS的底层实现。
- CAS的ABA问题和解决方法。
1.CAS的底层究竟是怎么来实现的?比如AtomicInteger,是怎么做到原子性的比较和更新一个值?
我们来看一下AtomicInteger的源代码
首先看一看AtomicInteger当中常用的自增方法 incrementAndGet:
private volatile int value;public final int get(){return value;
}public final int incrementAndGet(){for(;;){int current = get();int next = current+1;if(compareAndSet(current,next)){return next;}}
}
这段代码是一个无限循环,也就是CAS的自旋。循环体当中做了三件事:
- 获取当前值。
- 当前值+1,计算出目标值。
- 进行CAS操作,如果成功则跳出循环,如果失败则重复上述步骤。
这里需要注意的重点是 get 方法,这个方法的作用是获取变量的当前值。
如何保证获得的当前值是内存中的最新值呢?很简单,用volatile[ˈvɒlətaɪl]关键字来保证。有关volatile关键字的知识,我们之前有介绍过,这里就不详细阐述了。
compareAndSet是如何保证原子性操作的呢?
接下来看一看compareAndSet方法的实现,以及方法所依赖对象的来历:
private static final Unsafe unsafe = Unsafe.getUnsafe();private staitc final long valueOffset;static{try{valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclareField("value"));}catch(Exception ex){throw new Exception(ex);}
}public final boolean compareAndSet(int expect,int update){return unsafe.compareAndSwapInt(this,valueOffset,expect,update);
}
compareAndSet方法的实现很简单,只有一行代码。这里涉及到两个重要的对象,一个是unsafe,一个是valueOffset。
什么是unsafe呢?Java语言不像C,C++那样可以直接访问底层操作系统,但是JVM为我们提供了一个后门,这个后门就是unsafe。unsafe为我们提供了硬件级别的原子操作。
至于valueOffset对象,是通过unsafe.objectFieldOffset方法得到,所代表的是AtomicInteger对象value成员变量在内存中的偏移量。我们可以简单地把valueOffset理解为value变量的内存地址。
我们在上一期说过,CAS机制当中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B。
而unsafe的compareAndSwapInt方法参数包括了这三个基本元素:
- valueOffset参数代表了V
- expect参数代表了A
- update参数代表了B
正是unsafe的compareAndSwapInt方法保证了Compare和Swap操作之间的原子性操作。
2.ABA问题呢?
所谓ABA问题,就是一个变量的值从A改成B,又从B改成了A。
什么是ABA呢?假设内存中有一个值为A的变量,存储在地址V当中。

此时有三个线程想使用CAS的方式更新这个变量值,每个线程的执行时间有略微的偏差。线程1和线程2已经获得当前值,线程3还未获得当前值。

接下来,线程1先一步执行成功,把当前值成功从A更新为B;同时线程2因为某种原因被阻塞住,没有做更新操作;线程3在线程1更新之后,获得了当前值B。

再之后,线程2仍然处于阻塞状态,线程3继续执行,成功把当前值从B更新成了A。

最后,线程2终于恢复了运行状态,由于阻塞之前已经获得了“当前值”A,并且经过compare检测,内存地址V中的实际值也是A,所以成功把变量值A更新成了B。

这个过程中,线程2获取到的变量值A是一个旧值,尽管和当前的实际值相同,但内存地址V中的变量已经经历了A->B->A的改变。
表面看起来没毛病,本来就是要把A变成B,但如果我们结合实际应用场景,就可以看出它的问题所在。
当我们举一个提款机的例子。假设有一个遵循CAS原理的提款机,小灰有100元存款,要用这个提款机来提款50元。

由于提款机硬件出了点小问题,小灰的提款操作被同时提交两次,开启了两个线程,两个线程都是获取当前值100元,要更新成50元。
理想情况下,应该一个线程更新成功,另一个线程更新失败,小灰的存款只被扣一次。

线程1首先执行成功,把余额从100改成50。线程2因为某种原因阻塞了。这时候,小灰的妈妈刚好给小灰汇款50元。

线程2仍然是阻塞状态,线程3执行成功,把余额从50改成100。

线程2恢复运行,由于阻塞之前已经获得了“当前值”100,并且经过compare检测,此时存款实际值也是100,所以成功把变量值100更新成了50。

这个举例改编自《java特种兵》当中的一段例子。原本线程2应当提交失败,小灰的正确余额应该保持为100元,结果由于ABA问题提交成功了。
那么ABA问题如何解决呢?
解决方法很简单,加个版本号就行。
什么意思呢?真正要做到严谨的CAS机制,我们在Compare阶段不仅要比较期望值A和地址V中的实际值,还要比较变量的版本号是否一致。
我们仍然以最初的例子来说明一下,假设地址V中存储着变量值A,当前版本号是01。线程1获得了当前值A和版本号01,想要更新为B,但是被阻塞了。

这时候,内存地址V中的变量发生了多次改变,版本号提升为03,但是变量值仍然是A。

随后线程1恢复运行,进行Compare操作。经过比较,线程1所获得的值和地址V的实际值都是A,但是版本号不相等,所以这一次更新失败。

在Java当中,AtomicStampedReference类就实现了用版本号做比较的CAS机制。
3.总结
3.1 Java语言CAS底层如何实现?
利用unsafe提供了原子性操作方法。
3.2 什么是ABA问题?怎么解决?
当一个值从A更新成B,又更新会A,普通CAS机制会误判通过检测。
利用版本号比较可以有效解决ABA问题。
相关文章:
什么是CAS机制?
CAS和Synchronized的区别是什么?适合什么样的场景?有什么样的优点和缺点? 示例程序:启动两个线程,每个线程中让静态变量count循环累加100次。 public class ThreadTest {private static int count 0;public static …...
Java多态详解
下面讲解一下Java中的多态机制,力求用最通俗易懂的语言,最精炼的话语,最生动的例子,深入浅出Java多态,帮助读者轻松掌握这个知识点。 什么是多态? 多态是指同一种行为具有多个不同表现形式的能力。 多态…...
Android中简单实现Spinner的数据绑定
Android中简单实现Spinner的数据绑定 然后声明对象实例并加入到arraylist里面,并设置spinner的适配器 Spinner Sp (Spinner).............// List<CItem > lst new ArrayList<CItem>(); CItem ct new CItem ("1","测试"); lst.Add(ct)…...
【版本控制工具二】Git 和 Gitee 建立联系
文章目录 前言一、Git 和 Gitee 建立联系1.1 任意目录下,打开 git bash 命令行,输入以下命令生成公钥1.2 配置SSH公钥1.3 进行全局配置 二、其它相关Git指令2.1 常用指令2.2 指令操作可能出现的问题 三、补充3.1 **为什么要先commit,然后pull…...
最新AI智能创作系统ChatGPT商业源码+详细图文搭建部署教程+AI绘画系统
一、AI系统介绍 SparkAi创作系统是基于国外很火的ChatGPT进行开发的Ai智能问答系统。本期针对源码系统整体测试下来非常完美,可以说SparkAi是目前国内一款的ChatGPT对接OpenAI软件系统。那么如何搭建部署AI创作ChatGPT?小编这里写一个详细图文教程吧&am…...
【算法与数据结构】--目录
第一部分:算法基础 第一章:算法入门第二章:数据结构概述第三章:算法设计与分析 3.1 贪心算法3.2 动态规划3.3 分治算法3.4 回溯算法 第二部分:常见数据结构 第四章:数组和链表 4.1 数组4.2 链表4.3 比较…...
爱普生LQ1900KIIH复位方法
爱普生EPSON 1900KIIH是一部通用针式打印机,136列(10cpi下)的打印宽度,缓冲区128KB,打印速度为270字/秒。 打印机类型 打印方式:24针击打式点阵打印、打印方向:双向逻辑查找、安全规格标准&am…...
字段位置顺序对值的影响
Unity中验证AB加载场景时报错: Cannot load scene: Invalid scene name (empty string) and invalid build index -1 报错原因是因为把字段放在了Start函数后面(图一)改成(图二)就好了。图一中协程使用的sceneBName字段值为null。 图一: 图二:…...
pytorch_神经网络构建2(数学原理)
文章目录 深层神经网络多分类深层网络反向传播算法优化算法动量算法Adam 算法 深层神经网络 分类基础理论: 交叉熵是信息论中用来衡量两个分布相似性的一种量化方式 之前讲述二分类的loss函数时我们使用公式-(y*log(y_)(1-y)*log(1-y_)进行误差计算 y表示真实值,y_表示预测值 …...
Oracle SQL Developer 中查看表的数据和字段属性、录入数据
在Oracle SQL Developer中,选中一个表时,右侧会列出表的情况;第一个tab是字段的名称、数据类型等属性; 切换到第二个tab,显示表的数据; 这和sql server management studio不一样的; 看一下部门…...
java docker图片叠加水印中文乱码
java docker图片叠加水印中文乱码 技术交流博客 http://idea.coderyj.com/ 1.由于项目需要后端需要叠加图片水印,但是中文乱码,导致叠加了之后 中文是框框 2.经过多方查找基本都说在 linux下安装字体就解决了,但是尝试了均无效 3.后来忽然想到我的项目是用docker打包部署的,不…...
string类的使用方式的介绍
目录 前言 1.什么是STL 2. STL的版本 3. STL的六大组件 4.STL的缺陷 5.string 5.1 为什么学习string类? 5.1.1 C语言中的字符串 5.2 标准库中的string类 5.3 string类的常用接口的使用 5.3.1 构造函数 5.3.2 string类对象的容量操作 5.3.3 string类对象…...
FFmpeg 命令:从入门到精通 | 命令行环境搭建
FFmpeg 命令:从入门到精通 | 命令行环境搭建 FFmpeg 命令:从入门到精通 | 命令行环境搭建安装 FFmpeg验证 FFmpeg 是否安装成功 FFmpeg 命令:从入门到精通 | 命令行环境搭建 安装 FFmpeg 进入 FFmpeg 官网: 点击 Download&#…...
《从零开始学ARM》勘误
1. 50页 2 51页 3 236页 14.2.3 mkU-Boot 修改为: mkuboot 4 56页 修改为: 位[31:24]为条件标志位域,用f表示; 位[23:16]为状态位域,用s表示; 位[15:8]为扩展位域&…...
10款录屏软分析与选择使用,只看这篇文章就轻松搞定所有,高清4K无水印录屏,博主UP主轻松选择
录屏软件整理 如下为录屏软件,通过思维导图展示分析介绍: https://www.drawon.cn/template/details/6522bd5e0dad9029a0b528e1 如下为整理的录屏软件列表 名称产地价格支持的平台下载地址说明OBS国外免费开源windows/linux/machttps://obsproject.co…...
android: android:onClick=“@{() -> listener.onItemClick(viewModel)}“
一、前言:在我使用editTest控件的时候,它的下方有一条横线。我想把它去掉然后我在布局文件中这样写 android:background"null" 导致报错,报错信息是: android:onClick"{() -> listener.onItemClick(viewModel)…...
温故知新:dfs模板-843. n-皇后问题
n−n−皇后问题是指将 nn 个皇后放在 nnnn 的国际象棋棋盘上,使得皇后不能相互攻击到,即任意两个皇后都不能处于同一行、同一列或同一斜线上。 现在给定整数 nn,请你输出所有的满足条件的棋子摆法。 输入格式 共一行,包含整数 n…...
刷题笔记28——一直分不清的Kruskal、Prim、Dijkstra算法
图算法刷到这块,感觉像是走了一段黑路快回到家一样,看到这三个一直分不太清总是记混的名字,我满脑子想起的是大学数据结构课我坐在第一排,看着我班导一脸无奈,心想该怎么把这个知识点灌进木头脑袋里边呢。有很多算法我…...
Mysql时间同步设置
Mysql时间同步设置 当涉及到设置MySQL数据库时间与电脑同步时,实际的步骤可能会因操作系统和数据库版本的不同而有所差异。以下是一个基本的步骤示例,供您参考: 检查电脑时间: 首先确保电脑操作系统的时间是正确的。 设置MySQL时…...
如何理解分布式锁?
分布式锁的实现有哪些? 1.Memcached分布式锁 利用Memcached的add命令。此命令是原子操作,只有在key不存在的情况下,才能add成功,也就意味着线程得到了锁。 2.Reids分布式锁 和Memcached的方式类似,利用Redis的setn…...
告别环境报错!Windows下ESP8266开发环境保姆级搭建指南(含MSYS2、Python包避坑)
Windows下ESP8266开发环境零失败搭建全攻略 第一次接触ESP8266开发时,最令人头疼的莫过于环境配置。明明按照官方文档一步步操作,却总在某个环节卡住——Python包安装失败、工具链不兼容、串口识别异常...这些问题消耗了开发者大量时间。本文将彻底解决…...
Python报错Resource averaged_perceptron_tagger_eng not found
用python标注英文单词词形时,报错: import nltk nltk.download(‘averaged_perceptron_tagger_eng’) Resource averaged_perceptron_tagger_eng not found. 估计是因为网络问题,遂改用离线安装的方式。 第一步:下载averaged_perc…...
InterSystems IntelliCare 成为首个获得欧盟医疗器械法规认证的 AI 原生EHR系统
监管里程碑重申了 InterSystems 作为企业级 AI 应用领先提供商的地位 中国 北京 为全球超过 10 亿份健康记录提供支持的创新数据技术提供商 InterSystems今日宣布,其电子健康记录(EHR)解决方案已根据 《欧盟法规 (EU) 2017/745》ÿ…...
【2025 版】CMD 命令大全|超详细!零基础到精通,一篇封神✅
在Windows操作系统中,命令提示符(CMD)是一个强大的工具,允许用户通过输入命令来执行各种操作。无论是系统管理、网络配置,还是文件管理,CMD都能提供高效的解决方案。 一、基本命令 cd:更改目录…...
电池级氢氧化锂粉碎设备选型指南:氮气保护气流粉碎机详解
氢氧化锂(LiOH)具有易吸潮、强碱性、有一定粘附性的特点,且在电池级应用中对金属杂质污染零容忍。因此,行业主流不推荐传统的机械碾压式磨机(如雷蒙磨,容易引入铁屑且密封难),而是首…...
《微服务被吹上天了?我劝你别盲目跟风,这 5 种情况千万别用》
《微服务被吹上天了?我劝你别盲目跟风,这 5 种情况千万别用》 一、开头(钩子)“微服务不是银弹,而是毒药。很多团队用了微服务之后,开发效率反而下降了,系统复杂度反而上升了。”这句话不是我说…...
emacs-which-key替代方案对比:为什么它成为Emacs 30标准功能
emacs-which-key替代方案对比:为什么它成为Emacs 30标准功能 【免费下载链接】emacs-which-key Emacs package that displays available keybindings in popup 项目地址: https://gitcode.com/gh_mirrors/em/emacs-which-key emacs-which-key是一款能够在Ema…...
UE5污水智慧数字化运维供应商
在环保行业不断发展的今天,污水运维的数字化转型成为了众多企业关注的焦点。UE5技术凭借其强大的功能,为污水智慧数字化运维带来了新的变革。在众多供应商中,江苏天清世恒环保节能集团有限公司(以下简称“天清世恒”)凭…...
前端转行网络安全 漏洞挖掘赚钱前景分析
前言 最近,一个做运维的朋友跟我说他在学渗透测试。他说,公司请别人做渗透测试的费用是 2千/人天,一共2周。2周 2w 的收入,好香~ 于是,我也对渗透测试产生了兴趣。开始了探索之路~ 什么是渗透测试 渗透测试这名字听…...
避坑指南:你的Harmony和Seurat SCTransform连用顺序对了吗?一个参数引发的聚类差异
Harmony与Seurat SCTransform联用避坑指南:参数细节如何影响聚类结果 在单细胞RNA测序数据分析中,数据预处理和批次校正对最终结果的可靠性至关重要。许多研究者已经熟悉了Seurat流程中的SCTransform标准化方法和Harmony批次校正工具的基本使用ÿ…...
