一、引入
原子是世界上的最小單位,具有不可分割性。比如 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了。