Java中的CAS
CAS全稱 Compare And Swap(比較與交換),是一種無鎖演算法。在不使用鎖(沒有執行緒被阻塞)的情況下實現多執行緒之間的變數同步,java.util.concurrent包中的原子類就是通過CAS來實現了樂觀鎖。
基本介紹
CAS 操作包含三個運算元 —— 記憶體位置(V)、預期原值(A)和新值(B)。如果記憶體位置的值與預期原值相匹配,那麼處理器會自動將該位置值更新為新值。否則,處理器不做任何操作。
使用場景
有的時候我們需要對變數進行操作,如果是多執行緒,則有可能達不到我們的預期結果,Synchronized
等關鍵字,可以解決問題,但是Synchronized
關鍵字會讓沒有得到鎖資源的執行緒進入BLOCKED
public class CASTest { public static volatile int count = 0; public static AtomicInteger atomicCount = new AtomicInteger(); public static void main(String[] args) throws InterruptedException { for (int i = 0; i < 2; i++) { new Thread(() -> { for (int j = 0; j < 10000; j++) { count++; atomicCount.incrementAndGet(); } System.out.println("count++ end!"); }).start(); } Thread.sleep(2000); System.out.println(count); System.out.println(atomicCount.get()); } }
來看一下這個的程式碼輸出
count++ end!
count++ end!
18003
20000
可以看到volatile也沒有辦法保證運算的原子性。AtomicInteger使用CAS操作,可以保證運算的原子性。
AtomicInteger的實現原理
我們可以看一下AtomicInteger
中incrementAndGet()的原始碼
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
其是通過Unsafe來實現的,我們進入Unsafe中的getAndInt()方法中
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;
}
compareAndSwapInt是本地的方法,是通過CPU的cmpxchg指令,去比較暫存器中的 A 和 記憶體中的值 V。如果相等,就把要寫入的新值 B 存入記憶體中。如果不相等,就將記憶體值 V 賦值給暫存器中的值 A。然後通過Java程式碼中的while迴圈再次呼叫cmpxchg指令進行重試,直到設定成功為止。
CAS存在的問題
-
ABA問題CAS需要在操作值的時候檢查記憶體值是否發生變化,沒有發生變化才會更新記憶體值。但是如果記憶體值原來是A,後來變成了B,然後又變成了A,那麼CAS進行檢查時會發現值沒有發生變化,但是實際上是有變化的。ABA問題的解決思路就是在變數前面新增版本號,每次變數更新的時候都把版本號加一,這樣變化過程就從“A-B-A”變成了“1A-2B-3A”。JDK從1.5開始提供了
AtomicStampedReference
類來解決ABA問題。 -
併發高迴圈時間長的時候開銷大CAS操作如果長時間不成功,會導致其一直自旋,給CPU帶來非常大的開銷。
-
只能保證一個共享變數的原子操作對一個共享變數執行操作時,CAS能夠保證原子操作,但是對多個共享變數操作時,CAS是無法保證操作的原子性的。
Java從1.5開始JDK提供了
AtomicReference
類來保證引用物件之間的原子性,可以把多個變數放在一個物件裡來進行CAS操作。
參考
https://tech.meituan.com/2018/11/15/java-lock.html
歡迎關注公眾號:蜜蜂技術巢