CAS(compareAndSwap)原理

unsafe中對應擁有三個方法 compareAndSwapObject ,compareAndSwapIntcompareAndSwapLong ,他們都被標記爲nativehtml

compareAndSwapObject

它的核心實現爲java

oop res = oopDesc::atomic_compare_exchange_oop(x, addr, e);
複製代碼

實現核心以下linux

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壓縮指針,達到節約內存的目的。windows

compareAndSwapInt

核心代碼以下bash

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中函數聲明以下oop

//比較當前的值和目的地址的值,若是比較成功,就把目的地址的值更改成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中,一種實現以下ui

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中,實現以下atom

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,好比 AtomicIntegerincrementAndGet

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

相關文章
相關標籤/搜索