Java原子類及內部原理


一、引入

       原子是世界上的最小單位,具有不可分割性。比如 a=0;(a非long和double類型) 這個操作是不可分割的,那么我們說這個操作是原子操作。再比如:a++;

這個操作實際是a = a + 1;是可分割的,所以他不是一個原子操作。非原子操作都會存在線程安全問題,需要我們使用同步技術(sychronized)來讓它變成一

個原子操作。

       但是,像i++這種非原子操作,我們除了使用synchroinzed關鍵字實現同步外,還可以使用java.util.concurrent.atomic提供的線程安全的原子類來實現,例如

AtomicInteger、AtomicLong、AtomicReference等。下面我們就基於AtomicInteger為例,來看看其內部實現。

二、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;

這段代碼我們需要注意一下幾個方面:

(1)unsafe字段,AtomicInteger包含了一個Unsafe類的實例,unsafe就是用來實現CAS機制的,CAS機制我們在后面會講到;

(2)value字段,表示當前對象代碼的基本類型的值,AtomicInteger是int型的線程安全包裝類,value就代碼了AtomicInteger的值。注意,這個字段是volatile的。

(3)valueOfset,指是value在內存中的偏移量,也就是在內存中的地址,通過Unsafe.objectFieldOffset(Field f)獲取。這個值在使用CAS機制的時候會用到。

下面我們來看一個AtomicInteger類中的主要方法getAndIncrement(),也就相當於i++操作,只不過它是線程安全的,其實現代碼如下:

public final int getAndIncrement() {
for (;;) { int current = get(); int next = current + 1; if (compareAndSet(current, next)) return current; } }

       這個方法的做法為先獲取到當前的 value 屬性值,然后將 value 加 1,賦值給一個局部的 next 變量,然而,這兩步都是非線程安全的,但是內部有一個死循環,

不斷去做compareAndSet操作,直到成功為止,也就是修改的根本在compareAndSet方法里面。compareAndSet()方法的代碼如下:

public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
 }

      compareAndSet 傳入的為執行方法時獲取到的 value 屬性值,update為加 1 后的值, compareAndSet所做的為調用 Sun 的 UnSafe 的 compareAndSwapInt

方法來完成,此方法為 native 方法,compareAndSwapInt 基於的是CPU 的 CAS指令來實現的。下面我們將詳細的來介紹一下CAS的實現原理。

三、CAS機制

CAS是英文單詞Compare And Swap的縮寫,翻譯過來就是比較並替換。CAS機制當中使用了3個基本操作數:

(1)內存地址V,也就是AtomicInteger中的valueOffset。

(2)舊的預期值A,也就是getAndIncrement方法中的current。

(3)要修改的新值B,也就是getAndIncrement方法中的next。

CAS機制中,更新一個變量的時候,只有當變量的預期值A和內存地址V當中的實際值相同時,才會將內存地址V對應的值修改為B。下面我們來看一個具體的例子:

(1)在內存地址V當中,存儲着值為10的變量。

(2)此時線程1想要把變量的值增加1。對線程1來說,舊的預期值A=10,要修改的新值B=11。

(3)但是,在線程1要提交更新之前,另一個線程2搶先一步,把內存地址V中的變量值率先更新成了11。

(4)此時,線程1開始提交更新,首先進行A和地址V的實際值比較(Compare),發現A不等於V的實際值,提交失敗。

(5)線程1重新獲取內存地址V的當前值,並重新計算想要修改的新值。此時對線程1來說,A=11,B=12。這個重新嘗試的過程被稱為自旋。

(6)這一次比較幸運,沒有其他線程改變地址V的值。線程1進行Compare,發現A和地址V的實際值是相等的。

(7)線程1進行替換,把地址V的值替換為B,也就是12。

      對比Synchronized,我們可以發現,Synchronized屬於悲觀鎖,悲觀地認為程序中的並發情況嚴重,所以嚴防死守。CAS屬於樂觀鎖,樂觀地認為程序中的並發情況不那么

嚴重,所以讓線程不斷去嘗試更新。

但是CAS機制通常也存在以下缺點:

(1)ABA問題

       如果V的初始值是A,在准備賦值的時候檢查到它仍然是A,那么能說它沒有改變過嗎?也許V經歷了這樣一個過程:它先變成了B,又變成了A,使用CAS檢查時

以為它沒變,其實卻已經改變過了。

(2)CPU開銷較大

     在並發量比較高的情況下,如果許多線程反復嘗試更新某一個變量,卻又一直更新不成功,循環往復,會給CPU帶來很大的壓力。

(3)不能保證代碼塊的原子性

     CAS機制所保證的只是一個變量的原子性操作,而不能保證整個代碼塊的原子性。比如需要保證3個變量共同進行原子性的更新,就不得不使用Synchronized了。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM