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