CAS的一些感想
n++的問題不能保證原子操作。 因為被編譯後拆分成了3個指令,先獲取值,然後加一,然後寫回記憶體。
把變數宣告為volatile,volatile只能保證記憶體可見性,但是不能保證原子性,在多執行緒併發下,無法保證執行緒安全。
三個引數,一個當前記憶體值V、舊的預期值A、即將更新的值B,當且僅當預期值A和記憶體值V相同時,將記憶體值修改為B並返回true,
否則什麼都不做,並返回false。
CAS跟synchronized要達到的效果是一樣的,保證同一個時刻只有一個執行緒可以操作成功。
只不過CAS是硬體級別上面的鎖,會鎖住匯流排,保證只有一個CPU可以訪問記憶體,對於使用者層面來說應該是樂觀鎖,synchronized是JVM級別的鎖,開銷比較大
分析一下 AtomicInteger,變數value儲存的實際值,被volatile修飾,保證其值改變後,其他執行緒可以立馬看見。
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;} } public final int getAndAdd(intdelta) { 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; }
假設執行緒A和執行緒B同時執行getAndAdd操作(分別跑在不同CPU上):
假設AtomicInteger裡面的value原始值為3,即主記憶體中AtomicInteger的value為3,根據Java記憶體模型,執行緒A和執行緒B各自持有一份value的副本,值為3。
執行緒A通過getIntVolatile(var1, var2)拿到value值3,這時執行緒A被掛起。
執行緒B也通過getIntVolatile(var1, var2)方法獲取到value值3,運氣好,執行緒B沒有被掛起,並執行compareAndSwapInt方法比較記憶體值也為3,成功修改記憶體值為2。
這時執行緒A恢復,執行compareAndSwapInt方法比較,發現自己手裡的值(3)和記憶體的值(2)不一致,說明該值已經被其它執行緒提前修改過了,那隻能重新來一遍了。
重新獲取value值,因為變數value被volatile修飾,所以其它執行緒對它的修改,執行緒A總是能夠看到,執行緒A繼續執行compareAndSwapInt進行比較替換,直到成功。
在這裡的一個前提是compareAndSwapInt總是能拿到記憶體的最新值,然後把自己手上的值跟記憶體最新值比較,如果相等,即符合預期,則更新,如果不一致,進行下一輪
迴圈,重新獲取記憶體最新值。
底層是通過Unsafe這個類compareAndSwapInt進行比較替換,Unsafe類compareAndSwapInt這個是C++實現的,不同作業系統有不同的實現,它能直接操作的記憶體偏移地址,
拿到記憶體最新值,然後跟舊的值進行比較替換。它能在CPU級別實現鎖的機制,保證原子性
大致原理就是
1.如果是多處理器,為cmpxchg指令新增lock字首 ,單處理器,省略lock字首。
單處理器實際上不存在多個執行緒同時並行操作的場景,因為只有一個cpu,操作都是序列的,CPU根據時間片輪詢執行不同執行緒。
lock字首可以保證後續執行指令的原子性,老的處理器的做法是鎖住匯流排,開銷很大,新的處理器使用快取鎖定。