真实业务场景展现CAS原理的ABA问题及解决方案
文章目录
- 阅读提示
- CAS原理、ABA问题介绍
- 真实业务场景
- 如何解决ABA问题
- CAS学习总结
阅读提示
本文将借助开保险柜的业务场景重点阐述误用AtomicBoolean引起的ABA问题,以及解决方案。基于此,请先深入理解CAS原理,以及其会产生的ABA问题。关于CAS原理和ABA问题的优秀博客已经存在很多,所以本文只简单介绍CAS原理,希望读者有此基础。
CAS原理、ABA问题介绍
CAS(Compare and Swap)是一种乐观锁机制。CAS有3个操作数,预期值A,内存值V,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。
CAS算法实现一个重要前提需要线程A取出内存中某时刻的数据,而在下一时刻比较并替换,那么在这个时间差内的数据变化,线程A是无法感知。比如线程B在这个时间差内,就数据从A改成B,再从B改成A。线程A在下一时刻比较替换会成功。请你思考一下,这种ABA问题有什么危害?为什么要关心CAS原理带来的ABA问题?
比如,我们常用AtomicLong 表示车票数量 tickets,假设原始票数 tickets = 100,有一个线程A进行卖票,另一个线程B能增加票数,也能减少票数。当线程A进行卖票的时候,线程B增加10张票,又减少10张票,引起ABA问题,那么这个ABA问题对线程A有没有影响?如果有影响,会产生哪方面的线程安全问题(原子性、可见性、有序性)?如果没有影响,为什么没影响,不是说CAS会引起ABA问题吗,为什么这种场景不需要考虑ABA问题带来的影响。本文终极问题——到底什么情况下才要考虑解决CAS导致的ABA问题?
真实业务场景
// OPEN_OR_CLOSE 表示 保存高考卷的保险柜 开关,false表示close, true 表示 open,考虑下面的业务场景,
// 高考卷的保险柜,有且仅有两人有打开的权限。根据保密要求,人越少越好。当然,有且仅有一个人有权限,保密性更高,但是如果这人发生意外,就没人能打开保险柜
// 所以选两个人 既能照顾到保密性要求,又能减少突发事件的影响。
// 要求 2022-06-07 06:00:00 后,两个线程竞争去开保险柜,有且只有一人能打开,打开保险柜的人负责护送试题。(不考虑 synchronized 的实现方式)
// 假设这样的一种场景,张三、李四 竞争开柜的过程中,张三使手段让李四在开柜前,卡一下,确保自己能先开柜,然后拍照,获取试题,最后关上柜门
// 这个时候李四来开柜门,发现门的状态和教育部说的状态一样,都是close,然后李四拿走试题,张三过来说“李四啊,这次送试卷的任务就只能麻烦你了。”
// 然后 张三转手卖出试题,就算出了事情,教育厅也只能查李四。
这里先使用 AtomicBoolean 去实现,然后引出ABA问题。
public class AtomicBooleanExample {private static AtomicBoolean OPEN_OR_CLOSE = new AtomicBoolean(false);public static void main(String[] args) throws InterruptedException {boolean expect = OPEN_OR_CLOSE.get();boolean update = true;Thread zhangsan = new Thread(() -> {boolean isOpen = OPEN_OR_CLOSE.compareAndSet(expect, update);System.out.println(Thread.currentThread().getName() + "开柜门:" + isOpen);// 省略 偷试题的操作boolean isClose = OPEN_OR_CLOSE.compareAndSet(OPEN_OR_CLOSE.get(), expect);System.out.println(Thread.currentThread().getName() + "关柜门:" + isClose);System.out.println(Thread.currentThread().getName() + "偷题是否成功" + (isOpen && isClose));}, "zhangsan");Thread lisi = new Thread(() -> {try {// 张三使手段,确保自己先执行完,真实场景可能用其它的手段zhangsan.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "打开之前门状态为:" + OPEN_OR_CLOSE.get());boolean isOpen = OPEN_OR_CLOSE.compareAndSet(expect, update);System.out.println(Thread.currentThread().getName() + "打开之后门状态为:" + OPEN_OR_CLOSE.get() + ", " + Thread.currentThread().getName() + "开柜门是否成功:" + isOpen);}, "lisi");zhangsan.start();lisi.start();}
}
zhangsan开柜门:true
zhangsan关柜门:true
zhangsan偷题是否成功true
lisi打开之前门状态为:false
lisi打开之后门状态为:true, lisi开柜门是否成功:true
从结果可以看到,张三做了一次开关门操作(ABA操作)偷取了试题,然后李四顺利的打开保险柜,李四护送试题的同时,张三会卖出试题。显然,这时候使用AtomicBoolean 引起的ABA问题就需要我们去解决,因为我们关心保险柜门被打开的次数。保险柜门必须只能被打开一次。所以,我们需要记录保险柜门被操作的次数。Doug Lea 早就为JAVA 设计了AtomicStampedReference类以解决CAS的ABA问题。AtomicStampedReference的CAS操作是带有版本号的操作。
如何解决ABA问题
接下来用AtomicStampedReference 编写上面的程序。
public class AtomicStampedReferenceExample {private static AtomicStampedReference<Boolean> OPEN_OR_CLOSE = new AtomicStampedReference<>(false, 0);public static void main(String[] args) throws InterruptedException {boolean expectedReference = OPEN_OR_CLOSE.getReference();boolean newReference = true;int expectedStamp = OPEN_OR_CLOSE.getStamp();Thread zhangsan = new Thread(() -> {boolean isOpen = OPEN_OR_CLOSE.compareAndSet(expectedReference, newReference, expectedStamp, expectedStamp + 1);System.out.println(Thread.currentThread().getName() + "开柜门:" + isOpen);// 省略 偷试题的操作boolean isClose = OPEN_OR_CLOSE.compareAndSet(newReference, expectedReference, OPEN_OR_CLOSE.getStamp(), OPEN_OR_CLOSE.getStamp() + 1);System.out.println(Thread.currentThread().getName() + "关柜门:" + isClose);System.out.println(Thread.currentThread().getName() + "偷题是否成功" + (isOpen && isClose));}, "zhangsan");Thread lisi = new Thread(() -> {try {// 张三使手段,确保自己先执行完,真实场景可能用其它的手段zhangsan.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "打开之前门状态为:" + OPEN_OR_CLOSE.getReference());boolean isOpen = OPEN_OR_CLOSE.compareAndSet(expectedReference, newReference, expectedStamp, expectedStamp + 1);System.out.println(Thread.currentThread().getName() + "打开之后门状态为:" + OPEN_OR_CLOSE.getReference() + ", "+ Thread.currentThread().getName() + "开柜门是否成功:" + isOpen);}, "lisi");// 当然这个地方最好用 发令枪做,同时起跑zhangsan.start();lisi.start();}}
zhangsan开柜门:true
zhangsan关柜门:true
zhangsan偷题是否成功true
lisi打开之前门状态为:false
lisi打开之后门状态为:false, lisi开柜门是否成功:false
控制台输出 “lisi打开之前门状态为:false”,但是李四开柜门没有成功!!!因为一开始的 expectedStamp 是0,被张三操作两次后变成2,李四去开柜门的时候,拿着0的版本号去开,compareAndSet 发现版本号不一致,这个开门的操作就会失败。
CAS学习总结
再次学习CAS原理的过程中,我看了一些视频和一些博客,都是说CAS原理是什么,会带来ABA问题,ABA问题如何解决。但是都没说清楚,到底什么情况下要解决CAS导致的ABA问题。就好比师傅教你一招降龙十八掌对付坏人(ABA问题的解决方案),却没告诉你什么样的坏人(什么情况需要考虑解决ABA问题),你使出降龙十八掌才有效。
如果业务只关心Atomic系列类的值,不关心值的变化次数(ABA会增加两次操作),那么CAS导致的ABA问题就无需考虑,例如卖票问题,你只关心总票数,不关心总票数波动的次数——别人退票后的票数增加或者其他人买票后票数减少。
反之,如果业务关心CAS的操作次数,例如本文的保险柜开关次数,就需要引入版本号解决ABA问题。
某个对象,在某个状态只能被操作一次,即针对数值变化次数有要求,不是针对数值。
不过,也有种可能是ABA会导致整体数据错误,比如经典的链表换链头的例子,也是ABA 问题,但是它是链的数据变化了,其实不是针对次数,而是针对链中数据。
学而不思则罔,思而不学则殆。
真实业务场景展现CAS原理的ABA问题及解决方案相关推荐
- 【云原生|实践指北】5:真实业务场景下云原生项目落地实践学习
真实业务场景下云原生项目落地实践学习 写在前面的话 1.容器化的落地实践 搜题APP的云上之旅 2.Serverless的落地实践 某电商APP的Serverless改造之旅 3.云原生TKE的落地实 ...
- Java并发基石CAS原理以及ABA问题
在学习CAS之前,先从一个简单的案例入手,进而引出CAS的基本使用: 1.基于CAS的网站计数器 需求: 我们开发一个网站,需要对访问量进行统计,用户每发送一次请求,访问量+1,如何实现? 我们模拟有 ...
- 用真实业务场景告诉你,高并发下如何设计数据库架构?
目录: 用一个创业公司的发展作为背景引入 用多台服务器来分库支撑高并发读写 大量分表来保证海量数据下查询性能 读写分离来支撑按需扩容及性能提升 高并发下的数据库架构设计总结 这篇文章,我们来聊一下对于 ...
- 童玲:蚂蚁金服区块链在真实业务场景的实践与突破
本文转载于公众号 区块链新金融 2017年2月19日星期日下午,虎嗅网联合蚂蚁金服在上海举办了区块链上道沙龙.沙龙邀请到了蚂蚁金服首席架构师童玲.众安科技CTO李雪峰.中国电子技术标准化研究院主任兼中 ...
- 千万数据量下的真实业务场景SQL性能优化!
V-xin:ruyuanhadeng获得600+页原创精品文章汇总PDF 前 言 通过前几期文章的积累,现在我们的理论知识已经极为扎实了,这个时候就可以动手开始sql优化了,sql优化是非常重要,因为 ...
- 一文搞懂CAS,CAS原理分析及ABA问题详解
什么是CAS CAS即Compare And Swap的缩写,翻译成中文就是比较并交换,其作用是让CPU比较内存中某个值是否和预期的值相同,如果相同则将这个值更新为新值,不相同则不做更新,也就是CAS ...
- NLP最新趋势,7个主流业务场景!
1 深度之眼NLP项目实战安排 ⭐BAT级工程部署 项目意义:工程化部署是程序在开发完成之后,到线上正式运行整个过程中涉及到的多个环节的统称,主要包括:测试.GPU的分配和使用.微服务的封装.Dock ...
- 史上最复杂业务场景,逼出阿里高可用三大法宝
SREcon 是由计算机科学领域知名机构USENIX主办,聚焦网站可靠性.系统工程.以及复杂分布式系统相关的运维行业技术盛会,今年SREcon17大会 Asia/Australia站于当地时间5月22 ...
- 58技术主席:还原万亿级三高业务场景的设计与实践
孙玄,前58集团技术委员会主席,前转转二手交易平台首席架构师.今天想跟你聊点儿企业里那些年薪百万的架构师,他们的架构设计思维是如何升级的,我们来聊点儿干的! 01.百万年薪的核心竞争力 作为前58集团 ...
最新文章
- 在windows下安装python包管理器pip及使用
- 《软件设计师》——数据结构和算法基础
- Beetlsql自定义生成entity,mapper,md代码
- Yarn管理界面中Queue:root和Queue:default的区别
- 【APICloud系列|36】小米应用商店可以检测同个应用不同版本信息
- 插入排序:直接插入排序希尔排序
- 机器学习实战(MachineLearinginAction) 第一章
- 按创建日期删除指定日期之前的文件夹及文件夹下的所有子目录
- 算法---回溯法--模板解法
- Java-json系列(一):用GSON解析Json格式数据
- 非直接缓冲区与直接缓冲区
- nodejs实现同步http请求
- MSI GT60 16F4升级、超频、解锁功耗限制的研究
- python如何取消上一步操作的快捷键_ai返回上一步的快捷键是什么
- 写出一个程序,接受一个正浮点数值,输出该数值的近似整数值。如果小数点后数值大于等于5,向上取整;小于5,则向下取整。
- 毛毛虫 (树形dp)
- java实现小写转大写_人民币小写转大写(Java实现)
- 【Linux问题】Linux修改文件出现错误E45:“readonly” option is set(add ! to override)退出不了vim
- 交叉编译xorg-server
- OpenCV混合高斯模型前景分离