CAS(compareAndSwap)原理

66次阅读

共计 2565 个字符,预计需要花费 7 分钟才能阅读完成。

unsafe 中对应拥有三个方法 compareAndSwapObject ,compareAndSwapInt 和 compareAndSwapLong,他们都被标记为 native
compareAndSwapObject
它的核心实现为
oop res = oopDesc::atomic_compare_exchange_oop(x, addr, e);
实现核心如下
inline oop oopDesc::atomic_compare_exchange_oop(oop exchange_value,
volatile HeapWord *dest,
oop compare_value) {
if (UseCompressedOops) {
narrowOop val = encode_heap_oop(exchange_value);
narrowOop cmp = encode_heap_oop(compare_value);

narrowOop old = (narrowOop) Atomic::cmpxchg(val, (narrowOop*)dest, cmp);
return decode_heap_oop(old);
} else {
return (oop)Atomic::cmpxchg_ptr(exchange_value, (oop*)dest, compare_value);
}
}

UseCompressedOops: 32 位平台运行的程序在 64 位上会占用更大的长度,可以使用 -XX:+UserCompressedOops 压缩指针,达到节约内存的目的。
compareAndSwapInt
核心代码如下
return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
compareAndSwapLong
核心代码如下
if (VM_Version::supports_cx8())
return (jlong)(Atomic::cmpxchg(x, addr, e)) == e;
else {
jboolean success = false;
ObjectLocker ol(p, THREAD);
if (*addr == e) {*addr = x; success = true;}
return success;
}
supports_cx8: 判断硬件是不是支持 8 -byte compare-exchange,x86 架构中通过 cpuid 指令来获取是否试支持,CMPXCHG8 指令;SPARC 架构也是看 (_features & v9_instructions_m) 指令的支持情况
Atomic::cmpxchg
无论是那个调用,最终都归结到了 Atomic 上,Atomic.hpp 中函数声明如下
// 比较当前的值和目的地址的值,如果比较成功,就把目的地址的值更改为 exchange_value,并返回原来存的值
static jbyte cmpxchg (jbyte exchange_value, volatile jbyte* dest, jbyte compare_value);
static jint cmpxchg (jint exchange_value, volatile jint* dest, jint compare_value);
static jlong cmpxchg (jlong exchange_value, volatile jlong* dest, jlong compare_value);
static unsigned int cmpxchg(unsigned int exchange_value, volatile unsigned int* dest, unsigned int compare_value);
static intptr_t cmpxchg_ptr(intptr_t exchange_value, volatile intptr_t* dest, intptr_t compare_value);
static void* cmpxchg_ptr(void* exchange_value, volatile void* dest, void* compare_value);
从 Atomic.cpp 可以看到在不同的操作系统中有不同的实现在 windows_x86 中,一种实现如下
inline jint Atomic::cmpxchg (jint exchange_value, volatile jint* dest, jint compare_value) {
int mp = os::is_MP(); // 查看是否是多核
__asm {
mov edx, dest
mov ecx, exchange_value
mov eax, compare_value
LOCK_IF_MP(mp)
cmpxchg dword ptr [edx], ecx
}
}
linux_x86 中,实现如下
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;
}
可以看到最终都是使用操作系统对应的指令来完成
都在哪儿用了

可以看到 Atomic 的实现就是用的 CAS,比如 AtomicInteger 的 incrementAndGet
public final int incrementAndGet() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
}
}
这种一直循环的操作也称作自旋
CAS 的缺点

如果一直没有成功,则一直循环,给 CPU 带来很大的开销。
只能是一个变量
ABA 问题。一个变量取值为 A,恰巧另一个线程将它换成了 B 然后又换回来了,这个时候再读取还是 A,实际上是改变了值。java 自身提供了 AtomicStampedReference 来解决这个问题,原理是添加一个额外的版本来做判断

源码来自 jdk1.7

正文完
 0