主要參考html
★https://www.jianshu.com/p/fb6e91b013ccjava
http://zl198751.iteye.com/blog/1848575算法
https://www.xilidou.com/2018/02/01/java-cas/緩存
其餘參考安全
https://my.oschina.net/huangcongmin12/blog/692907多線程
http://www.cnblogs.com/dayhand/p/3713303.html併發
https://my.oschina.net/vshcxl/blog/795495app
http://www.javashuo.com/article/p-ckzisqmq-mv.htmljvm
https://mritd.me/2017/02/06/java-cas/函數
CAS: 全稱Compare and swap,字面意思:「比較並交換」,一個 CAS 涉及到如下操做:
假設內存中的原數據V,舊的預期值A,須要修改的新值B
當多個線程同時對某個資源進行CAS操做,只能有一個線程操做成功,可是並不會阻塞其餘線程,其餘線程只會收到操做失敗的信號。可見 CAS 實際上是一個樂觀鎖。
爲什麼說當多個線程同時對某個資源進行CAS操做,只能有一個線程操做成功?多個線程訪問,只有一次能成功的進行CAS操做(由於只有一個線程luck thread能夠成功的捕捉到A和V相等的那一刻,而後其餘線程訪問時A和V確定已經不相等了)。
CAS 算法大體原理是:在對變量進行計算以前(如 ++ 操做),首先讀取原變量值,稱爲 舊的預期值 A,而後在更新以前再獲取當前內存中的值,稱爲 當前內存值 V,若是 A==V 則說明變量從未被其餘線程修改過,此時將會寫入新值 B,若是 A!=V 則說明變量已經被其餘線程修改過,當前線程應當什麼也不作。
CAS是樂觀鎖,synchronized(重量級鎖)是悲觀鎖。
Java技術發展史先設計出的synchronized,synchronized會產生阻塞問題,後來又發展出了CAS。
閱讀JDK的源碼可知,J.U.C包其實是創建在CAS操做基礎上的。ReentrantLock這些類的底層其實就採用的CAS操做。
能夠用CAS在無鎖的狀況下實現原子操做,但要明確應用場合,很是簡單的操做且又不想引入鎖能夠考慮使用CAS操做,當想要非阻塞地完成某一操做也能夠考慮CAS。不推薦在複雜操做中引入CAS,會使程序可讀性變差,且難以測試,同時會出現ABA問題。
不想引入鎖或者想非阻塞的完成併發操做能夠考慮使用CAS操做。可是CAS操做的代碼可讀性差難測試,複雜操做最好不用引入CAS。
public class AtomicInteger extends Number implements java.io.Serializable { // setup to use Unsafe.compareAndSwapInt for updates private static final Unsafe unsafe = Unsafe.getUnsafe(); private static final long valueOffset; static { try { valueOffset = unsafe.objectFieldOffset (AtomicInteger.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } } private volatile int value; public final int get() { return value; } }
1.Unsafe,是CAS的核心類,因爲Java方法沒法直接訪問底層系統,須要經過本地(native)方法來訪問,Unsafe至關於一個後門,基於該類能夠直接操做特定內存的數據。
2.變量valueOffset,表示該變量值在內存中的偏移地址,由於Unsafe就是根據內存偏移地址獲取數據的。
3.變量value用volatile修飾,保證了多線程之間的內存可見性。
public final int getAndAdd(int delta) { return unsafe.getAndAddInt(this, valueOffset, delta); }
上述代碼已經使用到了Unsafe的方法。
public final int getAndAddInt(Object var1, long var2, int var4) { int var5; do { var5 = this.getIntVolatile(var1, var2); } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); return var5; }
假設線程A和線程B同時執行getAndAdd操做(分別跑在不一樣CPU上):
整個過程當中,利用CAS保證了對於value的修改的併發安全,繼續深刻看看Unsafe類中的compareAndSwapInt方法實現。
public final native boolean compareAndSwapInt(Object paramObject, long paramLong, int paramInt1, int paramInt2);
Unsafe類中的compareAndSwapInt,是一個本地方法,該方法的實現位於unsafe.cpp中。
UNSAFE_ENTRY(jboolean, 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; UNSAFE_END
先想辦法拿到變量value在內存中的地址。
經過Atomic::cmpxchg實現比較替換,其中參數x是即將更新的值,參數e是原內存的值。
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; }
__asm__表示彙編的開始
volatile表示禁止編譯器優化
LOCK_IF_MP是個內聯函數
#define LOCK_IF_MP(mp) "cmp $0, " #mp "; je 1f; lock; 1: "
inline jint Atomic::cmpxchg (jint exchange_value, volatile jint* dest, jint compare_value) { int mp = os::isMP(); //判斷是不是多處理器 _asm { mov edx, dest mov ecx, exchange_value mov eax, compare_value LOCK_IF_MP(mp) cmpxchg dword ptr [edx], ecx } } // Adding a lock prefix to an instruction on MP machine // VC++ doesn't like the lock prefix to be on a single line // so we can't insert a label after the lock prefix. // By emitting a lock prefix, we can define a label after it. #define LOCK_IF_MP(mp) __asm cmp mp, 0 \ __asm je L0 \ __asm _emit 0xF0 \ __asm L0:
LOCK_IF_MP根據當前系統是否爲多核處理器決定是否爲cmpxchg指令添加lock前綴。
1.若是是多處理器,爲cmpxchg指令添加lock前綴。
2.反之,就省略lock前綴。(單處理器會不須要lock前綴提供的內存屏障效果)
intel手冊對lock前綴的說明以下:
1.確保後續指令執行的原子性。
在Pentium及以前的處理器中,帶有lock前綴的指令在執行期間會鎖住總線,使得其它處理器暫時沒法經過總線訪問內存,很顯然,這個開銷很大。在新的處理器中,Intel使用緩存鎖定來保證指令執行的原子性,緩存鎖定將大大下降lock前綴指令的執行開銷。
2.禁止該指令與前面和後面的讀寫指令重排序。
3.把寫緩衝區的全部數據刷新到內存中。
上面的第2點和第3點所具備的內存屏障效果,保證了CAS同時具備volatile讀和volatile寫的內存語義。
java 的 cas 利用的的是 unsafe 這個類提供的 cas 操做。
unsafe 的cas 依賴了的是 jvm 針對不一樣的操做系統實現的 Atomic::cmpxchg
Atomic::cmpxchg 的實現使用了彙編的 cas 操做,並使用 cpu 硬件提供的 lock信號保證其原子性