深入理解Java多線程CAS比較並交換的底層原理

一.多線程環境下的自增操作

我們都知道在多線程情況下 i++ 的自增操作不是原子性的,因爲它分爲三個步驟:

  1. 取得i的值
  2. 將i的值+1
  3. 將新值寫回內存

所以我們會使用JUC包下的AtomicInteger類來進行int的原子操作:

public final int addAndGet(int delta) {
        return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
    }

該方法調用了Unsafe類實例的getAndAddInt方法,Unsafe類是java提供的可像C語言一樣直接操作內存的類。

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;
    }
    //由於是native方法所以其實際上不是Java語言編寫的
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

可以看出其內部是通過compareAndSwapInt函數實現的,JUC包下還有很多帶CompareAndSwap的方法,這些方法都用到了CAS


二.CAS介紹

CompareAndSwap 比較與交換,是併發編程中的一種編程機制,其內涵是:

  • CAS有3個操作數,內存值V,舊的預期值A,要修改的新值B。當且僅當預期值A和內存值V相同時,將內存值V修改爲B,否則什麼都不做
  • 整個JUC包都是建立在CAS機制上的

運行時,線程要從內存中取到內存值,在工作內存中比較內存值和工作內存的值,如果一致那麼將工作內存值寫會內存。這個過程涉及到JMM內存模型


JMM(Java Memory Model)本身是一種抽象的概念,並不真實存在,他描述的時一組規則或規範,通過這組規範定義了程序中各個變量(包括實例字段,靜態字段和構成數組對象的元素)的訪問方式

JMM的內容:每個線程創建時JVM都會爲其創建一個工作內存(有的成爲棧空間),工作內存是每個線程的私有數據區域,而java內存模型中規定所有變量都存儲在 主內存,主內存是貢獻內存區域,所有線程都可以訪問,但線程對變量的操作(讀取賦值等)必須在工作內存中進行,首先概要將變量從主內存拷貝到自己的工作內存空間,然後對變量進行操作,操作完成後再將變量寫回主內存,不能直接操作主內存中的變量,各個線程中的工作內存中存儲着主內存的變量副本拷貝


三.Unsafe類理解

public final int addAndGet(int delta) {
        return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
    }
  • 是CAS核心類,由於Java方法無法直接訪問地層系統,需要通過本地(native)方法來訪問,Unsafe相當於一個後門,基於該類可以直接操作特定內存數據。Unsafe類存在於sun.misc包中,其內部方法操作可以像C的指針一樣直接操作內存,因爲Java中CAS操作的執行依賴於Unsafe類的方法。

    Unsafe類中的所有方法都是native修飾的,也就是說Unsafe類中的方法都直接調用操作系統底層資源執行相應任務

  • 變量valueOffset,表示該變量值在內存中的偏移地址,因爲Unsafe就是根據內存便宜地址獲取數據的

  • 變量value用volatile修飾,保證多線程之間的可見性


四.CAS總結

CAS全稱呼Compare-And-Swap,它是一條CPU併發原語

他的功能是判斷內存某個位置的值是否爲預期值,如果是則更改爲新的值,這個過程是原子的。

CAS併發原語體現在JAVA語言中就是sun.misc.Unsafe類中各個方法。調用Unsafe類中的CAS方法,JVM會幫我們實現CAS彙編指令。這是一種完全依賴於硬件的功能,通過他實現了原子操作。由於CAS是一種系統原語,原語屬於操作系統用語範疇,是由若干條指令組成的,用於完成某個功能的一個過程,並且原語的執行必須是連續的,在執行過程中不允許被中斷,也就是說CAS是一條CPU的原子指令,不會造成數據不一致問題。

	//unsafe.getAndAddInt
    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;
    }

var1 AtomicInteger對象本身

var2 該對象的引用地址

var4 需要變動的數據

var5 通過var1 var2找出的主內存中真實的值

用該對象前的值與var5比較;

如果相同,更新var5+var4並且返回true,

如果不同,繼續去之然後再比較,直到更新完成



五.CAS缺點

  1. 循環時間長,開銷大

    例如getAndAddInt方法執行,有個do while循環,如果CAS失敗,一直會進行嘗試,如果CAS長時間不成功,可能會給CPU帶來很大的開銷

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

    對多個共享變量操作時,循環CAS就無法保證操作的原子性,這個時候就可以用鎖來保證原子性

  3. ABA問題

CAS算法實現一個重要前提需要去除內存中某個時刻的數據並在當下時刻比較並替換,那麼在這個時間差類會導致數據的變化。

比如線程1從內存位置V取出A,線程2同時也從內存取出A,並且線程2進行一些操作將值改爲B,然後線程2又將V位置數據改成A,這時候線程1進行CAS操作發現內存中的值依然時A,然後線程1操作成功。雖然CAS操作成功,但這個過程中存在問題。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章