多线程中的CAS
什么是CAS
CAS CompareAndSwap,或者 CompareAndSet,
是一个能够比较和替换的方法。
这个方法能够在多线程环境下保证对一个共享变量进行修改时的原子性不变。
通常,CAS方法会传递三个参数,
● 第一个参数V表示要更新的变量;
● 第二个参数E表示期望值;
● 第三个参数U表示更新后的值。更新的方式是:
如果V==E ,表示预期值和实际值相等,则将 V修改成U 并返回 true,
否则修改失败返回false。
CAS是如何实现的
在Java中的Unsafe类中提供了CAS方法,针对int类型变量的CAS方法定义如下:
public finlal native boolean compareAndSwapInt(Object o,long offset,int expect,int update);
它的四个参数:
● o,表示当前的实例对象
● offset,表示实例变量的内存地址偏移量
● expect,表示预期值
● update,表示要更新的值
compareAndSwapInt()是一个native方法,该方法是在JVM中定义和实现的。
基于compareAndSwapInt()方法,在JVM源码中的unsafe.cpp文件中,找到该方法:
UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))
UnsafeWrapper("Unsafe_CompareAndSwapInt");
//将Java对象转化为JVM中的对象
oop p = JNIHandles::resolve(obj);
//根据offset计算value的地址
jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
//比较addr和e是否相等,如果相等就把x赋值到目标字段,该方法返回修改之前的目标字段的值。
return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
UNSAFE_END
Atomic:compxchg() 方法的定义在atomic.cpp文件中:
unsigned Atomic::cmpxchg(unsigned int exchange_value,
volatile unsigned int* dest, unsigned int compare_value) {
assert(sizeof(unsigned int) == sizeof(jint), "more work to do");
return (unsigned int)Atomic::cmpxchg((jint)exchange_value, (volatile jint*)dest,
(jint)compare_value);
}
对于CAS操作,不同的操作系统和CPU架构,保证原子性的方法可能会不一样,而JVM是跨平台的语言,它需要在任何平台和CPU架构下都保持一致。Atomic:cmpxchg() 会在预编译阶段确定调用哪个平台下的重载。
以Linux为例,进入 atomic_linux_x86.inline.hpp中:
inline jint Atomic::cmpxchg (jint exchange_value, volatile jint* dest, jint compare_value) {
//判断是否多核CPU
int mp = os::is_MP();
//asm表示内嵌汇编代码
//volatile通知编译器对访问该变量的代码不再进行优化
//LOCK_IF_MP(%4)表示如果CPU是多核的,则需要为compxchgl指令增加一条Lock指令
__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;
}
上述代码的功能是基于汇编指令cmpxchgl 从主内存执行比较及替换的操作来实现数据的变更。在多核心CUP的情况下,为了保证多核心CPU下执行该命令的原子性,会增加一个Lock指令。CAS的底层用到了锁的机制。