0%

CAS

一、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
2
3
4
5
6
static JNINativeMethod methods_15[] = {
/* 省略一堆代码... */
{ CC "compareAndSwapInt", CC "("OBJ "J" "I" "I" ")Z", FN_PTR( Unsafe_CompareAndSwapInt ) },
{ CC "compareAndSwapLong", CC "("OBJ "J" "J" "J" ")Z", FN_PTR( Unsafe_CompareAndSwapLong ) },
/* 省略一堆代码... */
};
1
2
3
4
5
6
Unsafe_CompareAndSwapInt( JNIEnv * env, jobject unsafe, jobject obj, jlong offset, jint e, jint x ) ) {
UnsafeWrapper( "Unsafe_CompareAndSwapInt" );
oop p = JNIHandles::resolve( obj );
jint * addr = (jint *) index_oop_from_field_offset_long( p, offset );
return( (jint) (Atomic::cmpxchg( x, addr, e ) ) == e);
}
1
2
3
4
5
6
7
8
9
inline jint Atomic::cmpxchg( jint exchange_value, volatile jint*     dest, jint compare_value )
{
int mp = os::is_MP();
__asm__ volatile (LOCK_IF_MP( % 4 ) "cmpxchgl %1,(%3)"
: "=a" (exchange_value)
: "r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp)
: "cc", "memory");
return(exchange_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

您的支持将鼓励我继续分享!