深入解析Java AtomicInteger原子型別
在進行併發程式設計的時候我們需要確保程式在被多個執行緒併發訪問時可以得到正確的結果,也就是實現執行緒安全。執行緒安全的定義如下:
當多個執行緒訪問某個類時,不管執行時環境採用何種排程方式或者這些執行緒將如何交替執行,並且在主調程式碼中不需要任何額外的同步或協同,這個類都能表現出正確的行為,那麼這個類就是執行緒安全的。
執行緒安全需要實現兩點:
- 保證執行緒的記憶體可見性
- 保證原子性
舉個執行緒不安全的例子。假如我們想實現一個功能來統計網頁訪問量,你可能想到用count++ 來統計訪問量,但是這個自增操作不是執行緒安全的。count++ 可以分成三個操作:
- 獲取變數當前值
- 給獲取的當前變數值+1
- 寫回新的值到變數
假設count的初始值為10,當進行併發操作的時候,可能出現執行緒A和執行緒B都進行到了1操作,之後又同時進行2操作。A先進行到3操作+1,現在值為11;注意剛才AB獲取到的當前值都是10,所以B執行3操作後,count的值依然是11。這個結果顯然不符合我們的要求。
所以我們需要用本篇的主角—— AtomicInteger 來保證執行緒安全。
AtomicInteger 的原始碼如下:
package java.util.concurrent.atomic;
import sun.misc.Unsafe;
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;
public AtomicInteger(int initialValue) {
value = initialValue;
}
public AtomicInteger() {
}
public final int get() {
return value;
}
public final void set(int newValue) {
value = newValue;
}
public final void lazySet(int newValue) {
unsafe.putOrderedInt(this, valueOffset, newValue);
}
public final int getAndSet(int newValue) {
for (;;) {
int current = get();
if (compareAndSet(current, newValue))
return current;
}
}
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
public final boolean weakCompareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
public final int getAndIncrement() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return current;
}
}
public final int getAndDecrement() {
for (;;) {
int current = get();
int next = current - 1;
if (compareAndSet(current, next))
return current;
}
}
public final int getAndAdd(int delta) {
for (;;) {
int current = get();
int next = current + delta;
if (compareAndSet(current, next))
return current;
}
}
public final int incrementAndGet() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
}
}
public final int decrementAndGet() {
for (;;) {
int current = get();
int next = current - 1;
if (compareAndSet(current, next))
return next;
}
}
public final int addAndGet(int delta) {
for (;;) {
int current = get();
int next = current + delta;
if (compareAndSet(current, next))
return next;
}
}
public String toString() {
return Integer.toString(get());
}
public int intValue() {
return get();
}
public long longValue() {
return (long)get();
}
public float floatValue() {
return (float)get();
}
public double doubleValue() {
return (double)get();
}
}
複製程式碼
AtomicInteger 類中定義的屬性
// 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); }
}
複製程式碼
Unsafe是JDK內部的工具類,主要實現了平臺相關的操作。下面內容引自
sun.misc.Unsafe是JDK內部用的工具類。它通過暴露一些Java意義上說“不安全”的功能給Java層程式碼,來讓JDK能夠更多的使用Java程式碼來實現一些原本是平臺相關的、需要使用native語言(例如C或C++)才可以實現的功能。該類不應該在JDK核心類庫之外使用。
Unsafe的具體實現跟本篇的目標關聯不大,你只要知道這段程式碼是為了獲取value在堆記憶體中的偏移量就夠了。偏移量在AtomicInteger中很重要,原子操作都靠它來實現。
Value的定義和volatile
AtomicInteger 本身是個整型,所以最重要的屬性就是value,我們看看它是如何宣告value的
private volatile int value;
複製程式碼
我們看到value使用了volatile
修飾符,那麼什麼是volatile
呢?
volatile相當於synchronized
的弱實現,也就是說volatile
實現了類似synchronized
的語義,卻又沒有鎖機制。它確保對volatile
欄位的更新以可預見的方式告知其他的執行緒。
volatile
包含以下語義:
- Java 儲存模型不會對valatile指令的操作進行重排序:這個保證對volatile變數的操作時按照指令的出現順序執行的。
- volatile變數不會被快取在暫存器中(只有擁有執行緒可見)或者其他對CPU不可見的地方,每次總是從主存中讀取volatile變數的結果。也就是說對於volatile變數的修改,其它執行緒總是可見的,並且不是使用自己執行緒棧內部的變數。也就是在happens-before法則中,對一個valatile變數的寫操作後,其後的任何讀操作理解可見此寫操作的結果。
volatile 主要特性有兩點:1.防止重排序。2. 實現記憶體可見性 。記憶體可見性的作用是當一個執行緒修改了共享變數時,另一個執行緒可以讀取到這個修改後的值。在分析AtomicInteger 原始碼時,我們瞭解到這裡就足夠了。
用CAS操作實現原子性
AtomicInteger中有很多方法,例如incrementAndGet()
相當於i++
和getAndAdd()
相當於i+=n
。從原始碼中我們可以看出這幾種方法的實現很相似,所以我們主要分析incrementAndGet()
方法的原始碼。
原始碼如下:
public final int incrementAndGet() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
}
}
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
複製程式碼
incrementAndGet()
方法實現了自增的操作。核心實現是先獲取當前值和目標值(也就是value+1),如果compareAndSet(current, next)
返回成功則該方法返回目標值。那麼compareAndSet是做什麼的呢?理解這個方法我們需要引入CAS操作。
在大學作業系統課程中我們學過獨佔鎖和樂觀鎖的概念。獨佔鎖就是執行緒獲取鎖後其他的執行緒都需要掛起,直到持有獨佔鎖的執行緒釋放鎖;樂觀鎖是先假定沒有衝突直接進行操作,如果因為有衝突而失敗就重試,直到操作成功。其中樂觀鎖用到的機制就是CAS,全稱是Compare and Swap。
AtomicInteger 中的CAS操作就是compareAndSet()
,其作用是每次從記憶體中根據記憶體偏移量(valueOffset
)取出資料,將取出的值跟expect 比較,如果資料一致就把記憶體中的值改為update,如果資料不一致說明記憶體中的資料已經有過更新,此時就進行回滾(expect值不生效)操作。
這樣使用CAS的方法就保證了原子操作。Java中的AtomicLong、AtomicBoolean等方法的基本原理和思想跟AtomicInteger基本相同,本篇不再過多的解釋。
你可能會問,synchronized
關鍵字也可以實現併發操作啊,為什麼不用synchronized
呢?
其實我在沒研究AtomicInteger
原始碼之前,我認為其內部是用synchronized
來實現的原子操作。後來搜尋查了下發現原來用synchronized
實現原子操作 會影響效能,因為Java中的synchronized
鎖是獨佔鎖,雖然可以實現原子操作,但是這種實現方式的併發效能很差。
總結
總結一下,AtomicInteger 中主要實現了執行緒安全的整型操作,防止併發情況下出現異常結果,其內部主要依靠volatile 和JDK 中的unsafe 類操作記憶體中的資料來實現的。volatile 修飾符保證了value在記憶體中其他執行緒可以看到其值得改變,也就是實現了記憶體可見性。CAS操作保證了AtomicInteger 可以安全的修改value 的值,實現了原子性。volatile和CAS操作的組合實現了執行緒安全。