java并發(fā)編程-原子類

原子類

原子操作是指不會被線程調(diào)度機(jī)制打斷的操作,這種操作一旦開始,就一直運(yùn)行到結(jié)束,中間不會有任何線程上下文切換。

原子操作可以是一個步驟,也可以是多個操作步驟,但是其順序不可以被打亂,也不可以被切割而只執(zhí)行其中的一部分,將整個操作視作一個整體是原子性的核心特征。

而 java.util.concurrent.atomic 下的類,就是具有原子性的類,可以原子性地執(zhí)行添加、遞增、遞減等操作。比如之前多線程下的線程不安全的 i++ 問題,到了原子類這里,就可以用功能相同且線程安全的 getAndIncrement 方法來優(yōu)雅地解決。

作用

原子類的作用和鎖有類似之處,是為了保證并發(fā)情況下線程安全。不過原子類相比于鎖,有一定的優(yōu)勢:

粒度更細(xì):原子變量可以把競爭范圍縮小到變量級別,通常情況下,鎖的粒度都要大于原子變量的粒度。
效率更高:除了高度競爭的情況之外,使用原子類的效率通常會比使用同步互斥鎖的效率更高,因為原子類底層利用了 CAS 操作,不會阻塞線程。

原子類概覽

image.png

以 AtomicInteger 為例,分析在 Java 中如何利用 CAS 實現(xiàn)原子操作?

我們來看下 AtomicInteger 是如何通過 CAS 操作實現(xiàn)并發(fā)下的累加操作的,以其中一個重要方法 getAndAdd 方法為突破口。

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

可以看出,里面使用了 Unsafe 這個類,并且調(diào)用了 unsafe.getAndAddInt 方法。所以這里需要簡要介紹一下 Unsafe 類。

Unsafe 類

Unsafe 其實是 CAS 的核心類。由于 Java 無法直接訪問底層操作系統(tǒng),而是需要通過 native 方法來實現(xiàn)。不過盡管如此,JVM 還是留了一個后門,在 JDK 中有一個 Unsafe 類,它提供了硬件級別的原子操作,我們可以利用它直接操作內(nèi)存數(shù)據(jù)。

那么我們就來看一下 AtomicInteger 的一些重要代碼,如下所示:

public class AtomicInteger extends Number implements java.io.Serializable {
   // 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;
   public final int get() {return value;}
   ...
}

可以看出,在數(shù)據(jù)定義的部分,首先還獲取了 Unsafe 實例,并且定義了 valueOffset。往下看到 static 代碼塊,這個代碼塊會在類加載的時候執(zhí)行,執(zhí)行時會調(diào)用 Unsafe 的 objectFieldOffset 方法,從而得到當(dāng)前這個原子類的 value 的偏移量,并且賦給 valueOffset 變量,這樣一來就獲取到了 value 的偏移量,它的含義是在內(nèi)存中的偏移地址,因為 Unsafe 就是根據(jù)內(nèi)存偏移地址獲取數(shù)據(jù)的原值的,這樣就能通過 Unsafe 來實現(xiàn) CAS 了。

value 是用 volatile 修飾的,它就是原子類存儲的值的變量,由于它被 volatile 修飾,就可以保證在多線程之間看到的 value 是同一份,保證了可見性。

接下來繼續(xù)看 Unsafe 的 getAndAddInt 方法的實現(xiàn),代碼如下:

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;
}

首先看一下結(jié)構(gòu),它是一個 do-while 循環(huán),所以這是一個死循環(huán),直到滿足循環(huán)的退出條件時才可以退出。

do 后面的這一行代碼 var5 = this.getIntVolatile(var1, var2)是個 native 方法,作用就是獲取在 var1 中的 var2 偏移處的值。

傳入的兩個參數(shù),第一個就是當(dāng)前原子類,第二個是最開始獲取到的 offset,這樣一來就可以獲取到當(dāng)前內(nèi)存中偏移量的值,并且保存到 var5 里面。此時 var5 實際上代表當(dāng)前時刻下的原子類的數(shù)值。

現(xiàn)在再來看 while 的退出條件,也就是 compareAndSwapInt 這個方法,它一共傳入了 4 個參數(shù),這 4 個參數(shù)是 var1、var2、var5、var5 + var4,為了方便理解,給它們?nèi)×诵铝俗兞棵?,分別 object、offset、expectedValue、newValue,具體含義如下:

  • 第一個參數(shù) object 就是將要操作的對象,傳入的是 this,也就是 atomicInteger 這個對象本身;
  • 第二個參數(shù)是 offset,也就是偏移量,借助它就可以獲取到 value 的數(shù)值;
  • 第三個參數(shù) expectedValue,代表“期望值”,傳入的是剛才獲取到的 var5;
  • 最后一個參數(shù) newValue 是希望修改的數(shù)值 ,等于之前取到的數(shù)值 var5 再加上 var4,而 var4 就是之前所傳入的 delta,delta 就是希望原子類所改變的數(shù)值,比如可以傳入 +1,也可以傳入 -1。
    所以 compareAndSwapInt 方法的作用就是,判斷如果現(xiàn)在原子類里 value 的值和之前獲取到的 var5 相等的話,那么就把計算出來的 var5 + var4 給更新上去,所以說這行代碼就實現(xiàn)了 CAS 的過程。

一旦 CAS 操作成功,就會退出這個 while 循環(huán),但是也有可能操作失敗。如果操作失敗就意味著在獲取到 var5 之后,并且在 CAS 操作之前,value 的數(shù)值已經(jīng)發(fā)生變化了,證明有其他線程修改過這個變量。

這樣一來,就會再次執(zhí)行循環(huán)體里面的代碼,重新獲取 var5 的值,也就是獲取最新的原子變量的數(shù)值,并且再次利用 CAS 去嘗試更新,直到更新成功為止,所以這是一個死循環(huán)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容