CAS 即 compare and swap,比較並交換。java
CAS是一種原子操做,同時 CAS 使用樂觀鎖機制。安全
J.U.C中的不少功能都是創建在 CAS 之上,各類原子類,其底層都用 CAS來實現原子操做。用來解決併發時的安全問題。併發
i++
public class AddTest { public volatile int i; public void add() { i++; } }
經過javap -c AddTest
能夠看到add 方法的字節碼指令:性能
public void add(); Code: 0: aload_0 1: dup 2: getfield #2 // Field i:I 5: iconst_1 6: iadd 7: putfield #2 // Field i:I 10: return
i++
被拆分紅了多個指令:this
getfield
拿到原始內存值;iadd
進行加 1 操做;putfield
寫把累加後的值寫回內存。假設一種狀況:atom
線程 1
執行到iadd
時,因爲尚未執行putfield
,這時候並不會刷新主內存區中的值。線程 2
進入開始運行,剛剛將主內存區的值拷貝到私有內存區。線程 1
正好執行putfield
,更新主內存區的值,那麼此時線程 2
的副本就是舊的了。錯誤就出現了。最簡單的,在 add 方法加上 synchronized 。線程
public class AddTest { public volatile int i; public synchronized void add() { i++; } }
雖然簡單,而且解決了問題,可是性能表現並很差。code
最優的解法應該是使用JDK自帶的CAS方案,如上例子,使用AtomicInteger
類對象
public class AddIntTest { public AtomicInteger i; public void add() { i.getAndIncrement(); } }
CAS 的原理並不複雜:內存
拿 AtomicInteger
類分析,先來看看源碼:
我這裏的環境是Java11,若是是Java8這裏一些內部的一些命名有些許不一樣。
public class AtomicInteger extends Number implements java.io.Serializable { private static final long serialVersionUID = 6214790243416807050L; /* * This class intended to be implemented using VarHandles, but there * are unresolved cyclic startup dependencies. */ private static final jdk.internal.misc.Unsafe U = jdk.internal.misc.Unsafe.getUnsafe(); private static final long VALUE = U.objectFieldOffset(AtomicInteger.class, "value"); private volatile int value; //... }
Unsafe
類,該類對通常開發而言,少有用到。
Unsafe
類底層是用 C/C++ 實現的,因此它的方式都是被 native 關鍵字修飾過的。
它能夠提供硬件級別的原子操做,如獲取某個屬性在內存中的位置、修改對象的字段值。
關鍵點:
AtomicInteger
類存儲的值在 value
字段中,而value
字段被volatile
在靜態代碼塊中,而且獲取了 Unsafe
實例,獲取了 value
字段在內存中的偏移量 VALUE
接下回到剛剛的例子:
如上,getAndIncrement()
方法底層利用 CAS 技術保證了併發安全。
public final int getAndIncrement() { return U.getAndAddInt(this, VALUE, 1); }
getAndAddInt()
方法:
public final int getAndAddInt(Object o, long offset, int delta) { int v; do { v = getIntVolatile(o, offset); } while (!weakCompareAndSetInt(o, offset, v, v + delta)); return v; }
v
經過 getIntVolatile(o, offset)
方法獲取,其目的是獲取 o
在 offset
偏移量的值,其中 o
就是 AtomicInteger
類存儲的值,即value
, offset
內存偏移量的值,即 VALUE
。
重點,weakCompareAndSetInt
就是實現 CAS 的核心方法
o
和 v
相等,就證實沒有其餘線程改變過這個變量,那麼就把 v
值更新爲 v + delta
,其中 delta
是更新的增量值。分析:
AtomicInteger
的原始值爲 A,線程 1
和線程 2
各自持有一份副本,值都是 A。線程 1
經過getIntVolatile(o, offset)
拿到 value 值 A,這時線程 1
被掛起。線程 2
也經過getIntVolatile(o, offset)
方法獲取到 value 值 A,並執行weakCompareAndSetInt
方法比較內存值也爲 A,成功修改內存值爲 B。線程 1
恢復執行weakCompareAndSetInt
方法比較,發現本身手裏的值 A 和內存的值 B 不一致,說明該值已經被其它線程提早修改過了。線程 1
從新執行getIntVolatile(o, offset)
再次獲取 value 值,由於變量 value 被 volatile 修飾,具備可見性,線程A繼續執行weakCompareAndSetInt
進行比較替換,直到成功CAS是由CPU支持的原子操做,其原子性是在硬件層面進行保證的,在Java中普通用戶沒法直接使用,只能藉助atomic
包下的原子類使用,靈活性受限。
可是CAS只能保證單個變量操做的原子性,當涉及到多個變量時,CAS無能爲力。
原子性也不必定能保證線程安全,如在Java中須要與volatile
配合來保證線程安全。
CAS 有一個問題,舉例子以下:
線程 1
從內存位置 V 取出 A線程 2
也從內存位置 V 取出 A線程 1
處於掛起狀態,線程 2
將位置 V 的值改爲 B,最後再改爲 A線程 1
再執行,發現位置 V 的值沒有變化,符合指望繼續執行。此時雖然線程 1
仍是成功了,可是這並不符合咱們真實的指望,等於線程 2
狸貓換太子把線程 1
耍了。
這就是所謂的ABA問題
引入原子引用,帶版本號的原子操做。
把咱們的每一次操做都帶上一個版本號,這樣就能夠避免ABA問題的發生。既樂觀鎖的思想。
內存中的值每發生一次變化,版本號都更新。
在進行CAS操做時,比較內存中的值的同時,也會比較版本號,只有當兩者都沒有變化時,才能執行成功。
Java中的AtomicStampedReference
類即是使用版本號來解決ABA問題的。
在併發衝突機率大的高競爭環境下,若是CAS一直失敗,會一直重試,CPU開銷較大。
針對這個問題的一個思路是引入退出機制,如重試次數超過必定閾值後失敗退出。
更重要的是避免在高競爭環境下使用樂觀鎖。