CAS全稱 Compare And Swap(比較與交換),在不使用鎖的狀況下實現多線程之間的變量同步。屬於硬件同步原語,處理器提供了基本內存操做的原子性保證。juc包中的原子類就是經過CAS來實現了樂觀鎖。java
CAS算法涉及到三個操做數:算法
當且僅當 V 的值等於 A 時,CAS經過原子方式用新值B來更新V的值(「比較+更新」總體是一個原子操做),不然不會執行任何操做。
通常狀況下,「更新」是一個不斷重試的過程。segmentfault
JAVA中的sun.misc.Unsafe類,提供了多線程
等方法實現CAS。併發
看一下AtomicInteger的源碼定義:函數
public class AtomicInteger extends Number implements java.io.Serializable { private static final long serialVersionUID = 6214790243416807050L; // 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;
各屬性的做用:高併發
接着查看自增方法incrementAndGet
的源碼時,發現自增函數底層調用的是unsafe.getAndAddInt
。
可是因爲JDK自己只有Unsafe.class,只經過class文件中的參數名,並不能很好的瞭解方法的做用,因此咱們經過OpenJDK 8 來查看Unsafe的源碼:性能
// ------------------------- JDK 8 ------------------------- // AtomicInteger 的自增方法 public final int incrementAndGet() { return unsafe.getAndAddInt(this, valueOffset, 1) + 1; } // Unsafe.class 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; } // ------------------------- OpenJDK 8 ------------------------- // Unsafe.java public final int getAndAddInt(Object o, long offset, int delta) { int v; do { v = getIntVolatile(o, offset); } while (!compareAndSwapInt(o, offset, v, v + delta)); return v; }
由源碼可看出,getAndAddInt()循環獲取給定對象o中的偏移量處的值v,而後判斷內存值是否等於v。this
整個「比較+更新」操做封裝在compareAndSwapInt()中,經過JNI使用CPU指令完成的,屬於原子操做,能夠保證多個線程都可以看到同一個變量的修改值。spa
JDK經過CPU的cmpxchg指令,去比較寄存器中的 A 和 內存中的值 V。若是相等,就把要寫入的新值 B 存入內存中。若是不相等,就將內存值 V 賦值給寄存器中的值 A。而後經過Java代碼中的while循環再次調用cmpxchg指令進行重試,直到設置成功爲止。
自旋的實現讓全部線程都處於高頻運行,爭搶CPU執行時間的狀態。CAS操做若是長時間不成功,會致使其一直自旋,若是操做長時間不成功,會帶來很大的CPU資源消耗。
對一個共享變量執行操做時,CAS可以保證原子操做,可是對多個共享變量操做時,CAS是沒法保證操做的原子性的。
Java從1.5開始JDK提供了AtomicReference類來保證引用對象之間的原子性,能夠把多個變量放在一個對象裏來進行CAS操做。
CAS須要在操做值的時候檢查內存值是否發生變化,沒有發生變化纔會更新內存值。可是若是內存值原來是A,後來變成了B,而後又變成了A,那麼CAS進行檢查時會發現值沒有發生變化,可是其實是有變化的。
ABA問題的解決思路就是在變量前面添加版本號,每次變量更新的時候都把版本號加一,這樣變化過程就從「A-B-A」變成了「1A-2B-3A」。
JDK從1.5開始提供了AtomicStampedReference類來解決ABA問題,具體操做封裝在compareAndSet()中。
compareAndSet()首先檢查當前引用和當前標誌與預期引用和預期標誌是否相等,若是都相等,則以原子方式將引用值和標誌的值設置爲給定的更新值。
不過目前來講這個類比較」雞肋」,大部分狀況下 ABA 問題並不會影響程序併發的正確性,若是須要解決 ABA 問題,使用傳統的互斥同步可能比原子類更加高效。
只能保證一個共享變量的原子操做。對一個共享變量執行操做時,CAS可以保證原子操做,可是對多個共享變量操做時,CAS是沒法保證操做的原子性的。
Java從1.5開始JDK提供了AtomicReference類來保證引用對象之間的原子性,能夠把多個變量放在一個對象裏來進行CAS操做。
固然這都是由 Doug Lea 大佬親自爲 Java 操刀
計數器加強版,高併發下性能更好
頻繁更新但不太頻繁讀取的彙總統計信息時使用分紅多個操做單元,不一樣線程更新不一樣的單元
只有須要彙總的時候才計算全部單元的操做
T1執行後,A 變成了B
T3又開始執行了, B變成了A
T2開始執行, A變成了C
本文由博客一文多發平臺 OpenWrite 發佈!