一、CAS原子操作含义
CAS原子操作:“CAS”表示“Compare And Swap”,即“比较并替换”,具体是欲更新一个内存区域A值,输入两个参数——“待比较值”和“待更新值”,先将A当前值与“待比较值”比较,如果比较结果相等,则将A值更新为“待更新值”,否则失败返回
。
二、CAS原子操作存在的问题
2.1、ABA问题
有一个内存区域A,它的当前值为a,后来经历了“a-b”和“b-a”的值更新过程,此时如果对A执行CAS原子操作,“待比较值”为a,“待更新值”为d,则能够顺利执行,但是与预期并不一致,预期的内存区域A的值的迁移关系应该是a-d
,而不是a-b-a-d
。
解决方案是:另外再加一个版本号,在进行值更新的同时升版本号。
特别需要注意的是,“ABA问题”的描述只是一个统称,本质上来说,“ABA问题”包括两类:
- 偶数ABA问题,比如“A-A”,“A-B-C-A”,“A-B-C-D-E-A”,…
- 奇数ABA问题,比如“A-B-A”,“A-B-C-D-A”,“A-B-C-D-E-F-A”,…
2.2、只能保证替换一个内存区域值的操作的原子性
只能保证替换一个内存区域值的操作的原子性,而并不能保证替换大于1个内存区域值的操作的原子性。
三、指令集中的CAS原子操作指令
不同的指令集具有不同的CAS原子操作指令或者其等价。比如“在X86指令体系中,CMPXCHG
就是一个常见的CAS操作指令,需要注意的是,在多CPU或者CPU核环境中,需要给CMPXCHG
指令加上LOCK指令前缀
才能确保原子性”,“天生原子机器指令CAS”。
备注:
- 后续可知,加上
LOCK指令前缀
,不仅使得“原子化”,也使得“禁止重排序”,“刷新缓存到内存”和“使缓存失效”,这里给CMPXCHG
指令加上LOCK指令前缀
,只是为了“原子化”,后三者效果只是顺带,另外,CAS原子操作指令并不只有LOCK CMPXCHG
这种形式,因此,在通用使用CAS原子操作指令时并不能作具有“禁止重排序”,“刷新缓存到内存”和“使缓存失效”效果的假定
四、JDK中提供的CAS原子方法
JDK中提供的CAS原子方法是“sun.misc.Unsafe”类下的compareAndSwapObject()
,compareAndSwapInt()
和compareAndSwapLong()
方法。
查看这3个方法的本地实现(C/C++语言源代码),可知核心就是直接调用LOCK CMPXCHAG
指令(在X86指令体系中)。
以compareAndSwapInt()
方法为例,跟踪其本地实现源代码如下:
1 | static JNINativeMethod methods_15[] = { |
1 | Unsafe_CompareAndSwapInt( JNIEnv * env, jobject unsafe, jobject obj, jlong offset, jint e, jint x ) ) { |
1 | inline jint Atomic::cmpxchg( jint exchange_value, volatile jint* dest, jint compare_value ) |
可知在执行compareAndSwapInt()
方法时,最后会执行到LOCK_IF_MP( % 4 ) "cmpxchgl %1,(%3)"
语句,其中LOCK_IF_MP( % 4 )
表示在单CPU且单核的环境中,可以优化不加LOCK指令前缀
,否则就需要加LOCK指令前缀
以确保cmpxchgl
的原子性,而cmpxchgl
表示一个属于CMPXCHG
家族的CAS操作指令,它的末尾l
表示操作数长度为4。
参考文献
[1]https://juejin.im/post/5a73cbbff265da4e807783f5
[2]https://stackoverflow.com/questions/27837731/is-x86-cmpxchg-atomic-if-so-why-does-it-need-lock