1. 程式人生 > 其它 >17. JUC你瞭解CAS嗎

17. JUC你瞭解CAS嗎

技術標籤:JUCjava多執行緒

1. CAS是什麼

CAS的全稱為Compare-And-Swap ,它是一條CPU併發原語,它的功能是判斷記憶體某個位置的值是否為預期值,如果是則更改為新的值,否則繼續比較直到主記憶體和工作記憶體中的值一致為止,整個過程是原子的。

CAS併發原語體現在Java語言中就是sun.misc.Unsafe類中的各個方法。

呼叫Unsafe類中的CAS方法,JVM會幫我們實現CAS彙編指令。

這是一種完全依賴於硬體的功能,通過它實現了原子操作。

再次強調,由於CAS是一種系統原語,原語屬於作業系統用語範疇,是由若干條指令組成,用於完成某個功能的一個過程,並且原語的執行必須是連續的,在執行過程中不允許中斷,也就是說CAS是一條CPU的原子指令,不會造成所謂的資料不一致問題。

2. 比較並交換

  1. 主實體記憶體中有1個5,此時有兩個執行緒要對其操作
  2. A、B執行緒都對主實體記憶體的這個值進行了副本拷貝
  3. A執行緒從主實體記憶體中取走的時候是5,要寫入主實體記憶體時發現主實體記憶體的值還是5,這說明沒有人動過,此時成功將值修改為2019
  4. B執行緒當它要寫入主實體記憶體,判斷拿走的是否與主實體記憶體一致時,發現主實體記憶體的值已經被修改過了,則修改失敗,主實體記憶體的值保持不變。
public class CASDemo {
    public static void main(String[] args) {
        AtomicInteger atomicInteger =
new AtomicInteger(5); System.out.println(atomicInteger.compareAndSet(5, 2021) + "\t" + atomicInteger.get()); System.out.println(atomicInteger.compareAndSet(5, 2022) + "\t" + atomicInteger.get()); } }

結論:如果執行緒的期望值與主實體記憶體的真實值一樣,那就修改為更新值,返回true,如果不一樣,則修改失敗,返回false。

3. CAS的底層原理

Unsafe類加自旋

談談你對Unsafe的理解

AtomicInteger.java

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();
	
    // 表示該變數在記憶體中的偏移地址,因為Unsafe就是根據記憶體偏移地址獲取資料的
    private static final long valueOffset;

    static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

    // value用volatile修飾,保證多執行緒之間的可見性
    private volatile int value;
    
    //...
}

Unsafe存在於sun.misc包中,是CAS的核心類

由於Java 方法無法直接訪問底層 ,所以需要通過本地(native)方法來訪問

而Unsafe類所有的方法都是native修飾的,它的內部方法可以像C的指標一樣直接操作記憶體

就是直接呼叫作業系統底層資源執行相應任務,相當於Java直接呼叫作業系統資源的中間類,所以Java中CAS的執行依賴於Unsafe類的方法

底層原理詳解

AtomicInteger.java

  • this:代表當前物件
  • valueOffset:記憶體偏移地址
  • 1:需要變動的數量

Unsafe.class

  • var1:代表當前物件
  • var2:記憶體偏移地址
  • var4:需要變動的數量
  • var5:是通過var1跟var2找出的主記憶體中真實的值

image-20210106210021643

  1. AtomicInteger裡面的value原始值為3,即主記憶體中AtomicInteger的value為3,根據JMM模型,執行緒A和執行緒B分別在工作記憶體中持有一份值為3的value的副本
  2. 執行緒A通過getIntVolatile(var1,var2)拿到value值3,這時執行緒A被掛起
  3. 執行緒B也通過getIntVolatile(var1,var2) 拿到value值3,此時剛好執行緒B沒有被掛起並執行了compareAndSwapInt方法,結果比較記憶體中的值也是3,成功修改記憶體值為4,執行緒B打完收工 ,一切OK
  4. 這時執行緒A恢復,執行compareAndSwapInt方法比較,發現自己手裡的數值和記憶體中的數字4不一致,說明該值已經被其他執行緒搶先修改了,A執行緒修改失敗,只能重新再來
  5. 執行緒A重新獲取value值,因為變數value是volatile修飾,所以修改對其它的執行緒是可見的,執行緒A繼續執行compareAndSwapInt方法進行比較替換,直到成功

4. CAS缺點

  1. 迴圈時間長開銷大

    在getAndAddInt方法中有個do while,如果CAS失敗,會一直進行嘗試,如果CAS長時間一直不成功,則會給CPU帶來很大的開銷。

  2. 只能保證一個共享變數的原子操作

    對一個共享變數執行操作時,我們可以使用CAS的方式來保證原子性,但是對多個共享變數操作時,迴圈CAS就無法保證操作的原子性了,這個時候就需要用鎖來保證原子性

  3. 引出來ABA問題?

總結:

  1. synchronized加鎖,一致性保證,併發性下降
  2. CAS不加鎖,保證一致性,但是需要多次比較,耗費時間長,開銷較大。