CAS,Compare and Swap即比較並替換,設計併發算法時經常使用到的一種技術,Doug lea大神在java同步器中大量使用了CAS技術,鬼斧神工的實現了多線程執行的安全性。java
目前的處理器基本都支持CAS,只不過不一樣的廠家的實現不同罷了。CAS有三個操做數:內存值V、舊的預期值A、要修改的值B,當且僅當預期值A和內存值V相同時,將內存值修改成B並返回true,不然什麼都不作並返回false。c++
先看一段代碼算法
public int a = 1; public boolean compareAndSwapInt(int b) { if (a == 1) { a = b; return true; } return false; }
試想這段代碼在多線程併發下,會發生什麼?咱們不妨來分析一下:緩存
CAS中的比較和替換是一組原子操做,不會被外部打斷,先根據paramLong/paramLong1獲取到內存當中當前的內存值V,在將內存值V和原值A做比較,要是相等就修改成要修改的值B,屬於硬件級別的操做,效率比加鎖操做高。安全
java.util.concurrent.atomic包下的原子操做類都是基於CAS實現的,接下去咱們經過AtomicInteger來看看是如何經過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;} }
接下去,咱們看看AtomicInteger是如何實現併發下的累加操做:併發
//jdk1.8實現 public final int getAndAdd(int delta) { return unsafe.getAndAddInt(this, valueOffset, delta); } 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; }
在jdk1.8中,比較和替換操做放在unsafe類中實現。this
假設如今線程A和線程B同時執行getAndAdd操做:atom
整個過程當中,利用CAS保證了對於value的修改的線程安全性。spa
咱們繼續深刻看看Unsafe類中的compareAndSwapInt方法。
public final native boolean compareAndSwapInt(Object paramObject, long paramLong, int paramInt1, int paramInt2);
能夠看到,這是一個本地方法調用,這個本地方法在openjdk中依次調用c++代碼:unsafe.cpp,atomic.cpp,atomic_window_x86.inline.hpp。下面是對應於intel X86處理器的源代碼片斷。
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 } }
從上面的源碼中能夠看出,會根據當前處理器類型來決定是否爲cmpxchg指令添加lock前綴。
intel手冊對lock前綴的說明以下:
上面的第2點和第3點所具備的內存屏障效果,保證了CAS同時具備volatile讀和volatile寫的內存語義。
CAS存在一個很明顯的問題,即ABA問題。若是變量V初次讀取的時候是A,而且在準備賦值的時候檢查到它仍然是A,那能說明它的值沒有被其餘線程修改過了嗎?若是在這段期間它的值曾經被改爲了B,而後又改回A,那CAS操做就會誤認爲它歷來沒有被修改過。針對這種狀況,java併發包中提供了一個帶有標記的原子引用類"AtomicStampedReference",它能夠經過控制變量值的版原本保證CAS的正確性。