【Java8原始碼分析】併發包-AtomicInteger
AtomicInteger類是實現了原子操作的Integer,以往對於保證int、double、float等基礎型別的運算原子性,需要採用加鎖的方式。但是為了一個簡單的運算操作採用鎖,在多執行緒競爭嚴重的情況下,會導致效能降低,所以在java1.5推出了Atomic包,是採用CAS演算法實現原子性運算。
一、CPU鎖
CPU鎖有如下3種類型:
(1)處理器自動保證基本記憶體操作的原子性
首先處理器會自動保證基本的記憶體操作的原子性。 處理器保證從系統記憶體當中讀取或者寫入一個位元組是原子的, 意思是當一個處理器讀取一個位元組時, 其他處理器不能訪問這個位元組的記憶體地址。
(2)使用匯流排鎖保證原子性(開銷大)
所謂匯流排鎖就是使用處理器提供的一個 LOCK# 訊號,當一個處理器在總線上輸出此訊號時, 其他處理器的請求將被阻塞住, 那麼該處理器可以獨佔使用共享記憶體。
(3)用快取鎖保證原子性
所謂“快取鎖定”就是如果快取在處理器快取行中記憶體區域在 LOCK 操作期間被鎖定,當它執行鎖操作回寫記憶體時,處理器不在總線上聲言 LOCK# 訊號,而是修改內部的記憶體地址,並允許它的快取一致性機制來保證操作的原子性,因為快取一致性機制會阻止同時修改被兩個以上處理器快取的記憶體區域資料,當其他處理器回寫已被鎖定的快取行的資料時會起快取行無效
二、CAS演算法
當前的處理器基本都支援CAS(Compare and swap),只不過每個廠家所實現的演算法並不一樣罷了,每一個CAS操作過程都包含三個運算子:一個記憶體地址V,一個期望的值A和一個新值B,操作的時候如果這個地址上存放的值等於這個期望的值A,則將地址上的值賦為新值B,否則不做任何操作。CAS的基本思路就是,如果這個地址上的值和期望的值相等,則給其賦予新值,否則不做任何事兒,但是要返回原值是多少。
簡單地說,CAS使得同步並不阻塞在程式語言層面上,而是阻塞在硬體層面上。
CAS類似資料庫的樂觀鎖。
三、原始碼分析
public class AtomicInteger extends Number implements java.io.Serializable {
private static final long serialVersionUID = 6214790243416807050L;
// Unsafe類只能在JDK內部使用
private static final Unsafe unsafe = Unsafe.getUnsafe();
// AtomicInteger中value的偏移值
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 AtomicInteger(int initialValue) {
value = initialValue;
}
public AtomicInteger() {
}
public final int get() {
return value;
}
// set()是直接對value進行操作的,不需要CAS,因為單步操作就是原子操作
public final void set(int newValue) {
value = newValue;
}
public final void lazySet(int newValue) {
// 這是一個有序或者有延遲的方法,並且不保證值的改變被其他執行緒立即看到
// 但因為value域設定為volatile,故是有效的
unsafe.putOrderedInt(this, valueOffset, newValue);
}
// 獲取舊值,設定新值
public final int getAndSet(int newValue) {
return unsafe.getAndSetInt(this, valueOffset, newValue);
}
// CAS演算法包裝
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
// 根據名字判斷是先獲取,還是先操作
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
public final int getAndDecrement() {
return unsafe.getAndAddInt(this, valueOffset, -1);
}
public final int getAndAdd(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta);
}
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
public final int decrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, -1) - 1;
}
public final int addAndGet(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
}
// 以下函式採用自旋的方式,計算新值,然後呼叫CAS判斷是否可以更新
// 不能更新重新計算
public final int getAndUpdate(IntUnaryOperator updateFunction) {
int prev, next;
do {
prev = get();
next = updateFunction.applyAsInt(prev);
} while (!compareAndSet(prev, next));
return prev;
}
public final int updateAndGet(IntUnaryOperator updateFunction) {
int prev, next;
do {
prev = get();
next = updateFunction.applyAsInt(prev);
} while (!compareAndSet(prev, next));
return next;
}
public final int getAndAccumulate(int x,
IntBinaryOperator accumulatorFunction) {
int prev, next;
do {
prev = get();
next = accumulatorFunction.applyAsInt(prev, x);
} while (!compareAndSet(prev, next));
return prev;
}
public final int accumulateAndGet(int x,
IntBinaryOperator accumulatorFunction) {
int prev, next;
do {
prev = get();
next = accumulatorFunction.applyAsInt(prev, x);
} while (!compareAndSet(prev, next));
return next;
}
}
四、CAS的缺點
(1)ABA問題
因為 CAS 需要在操作值的時候檢查下值有沒有發生變化,如果沒有發生變化則更新,但是如果一個值原來是A,變成了B,又變成了A,那麼使用CAS進行檢查時會發現它的值沒有發生變化,但是實際上卻變化了。ABA問題的解決思路就是使用版本號。
(2)自旋操作有可能開銷很大
自旋 CAS 如果長時間不成功,會給 CPU 帶來非常大的執行開銷。
(3)只能保證一個共享變數的原子操作
當對一個共享變數執行操作時,我們可以使用迴圈CAS的方式來保證原子操作,但是對多個共享變數操作時,迴圈CAS就無法保證操作的原子性,這個時候就可以用鎖