ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

Java原子类中可以靠版本号比较为什么还需要CAS操作?以及ABA问题到底是什么?

2022-03-21 20:58:19  阅读:205  来源: 互联网

标签:ABA Java 版本号 原子 nX CAS 线程


https://www.zhihu.com/question/269109328

问题

最近面试面试官提到java原子类可以通过CAS保证操作的原子性,但缺点是产生了ABA问题,所以可通过版本号比较。那为什么不直接通过版本号比较呢? 这是一个问题,我还有一个疑惑就是ABA问题在什么情况下会造成影响,值一样不就行了吗。

回答

1

的确可以只比较版本号,实际上一些外部KV存储的CAS经常就是只比较版本号的,但是你的目标并不是原子性地递增一个版本,而是原子性地在递增版本的同时设置值,保证这两个操作一起形成一个原子操作。所以需要拼一下。如果只想看版本是否更新,就不需要。

CAS的价值并不是说最后更新成正确的值就好,如果这样那只要每次都直接set就好了。它的价值是形成乐观锁,保证在两次commit之间没有其他commit,很多情况下从读取前一个值到CAS之间的过程中,并不仅仅用到了这个数值。

最典型的情况下,这个值可以是个引用(指针),操作者读取了引用指向的对象内部的内容,创建新对象,然后将新对象通过CAS设置上去,完成一次事务。如果这个过程中发生了ABA,回到A的时候,虽然指向的地址没有变化,但对象内部的内容可能发生了变化,这就会导致错误

2

我觉得java的AtomicStampedReference确实蛮坑爹的,每次cas不管成功失败都会new出一个对象,性能有一定影响。最好的解法是像c++的folly库一样能偷64位指针的高16位做stamp,但是java语言层面规定的太死,玩不了这种hack。

不要用Reference,而用数组下标做指针,用Atomic记录下标,偷高位的bit做stamp,可以绕开AtomicStampedReference,但这样GC又成了问题…反正挺麻烦的。

ABA给你举个例子你就懂了,假如我有个链表W->X->Y->Z,我要删除X,如果我只做W.next.CompareAndSet(X, X.next)的话,如果这个线程CAS前被挂起,另一个线程删掉了X,X被GC了,又另一个线程new了个node,叫nX好了,插入到W和Y中间,而刚好分配给他X原来的地址,链表就变成了 W->nX->Y->Z,这时候第一个线程醒了,CompareAndSet仍然会成功,因为X和nX地址一样,就把nX删掉了,而逻辑上X和nX是不同的node,第一个线程逻辑正确的操作是不去动链表,返回false(没找到X)。如果用了StampedReference每次修改以后stamp+1,就可以避免这种情况。

3

不熟悉什么java原子类怎么还有版本号一说,CAS是动作,版本号是为了确认引用的对象还之前读到的那个。

ABA是指引用(指针)值没变,但实际对象变化了(新生成的),之前那个已经被回收了,所以版本号严格递增,就知道这个改变是否发生过了。

理论上版本号也不是一个合法的方案,因为版本号也会溢出循环。实际应用中考虑这个时间周期一般很长,就认为几乎不可能遇到。如果是为了合成为一个原子操作,那么就肯定都要比较了,因为CAS不是为检查版本号而设计的,而且它被发明的时候没有理论的指导,就是觉得简单又强大。

后来理论上说的是只要CAS就能干一大票事了,但实践中想要达到理论的效果,似乎又总有点力不从心的地方,所以变着花样从各个角度去增强它。

标签:ABA,Java,版本号,原子,nX,CAS,线程
来源: https://blog.csdn.net/fengyuyeguirenenen/article/details/123645628

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有