Java開發中不可避免的會遇到並發的問題。在進行並發編程的時候我們需要確保程序在被多個線程並發訪問時可以得到正確的結果,也就是要實現線程安全。
那么什么樣的標准可以稱為線程安全呢?這里有線程安全的定義:
當多個線程訪問某個類時,不管運行時環境采用何種調度方式或者這些線程將如何交替執行,並且在主調代碼中不需要任何額外的同步或協同,這個類都能表現出正確的行為,那么這個類就是線程安全的。
舉一個線程不安全的小例子。假如我們想實現一個功能來統計網頁訪問量,首先我們可能想到用count++
的方法來統計訪問量。count++
其實可以分成三個獨立的操作:
- 獲取變量當前值
- 給獲取的當前變量值+1
- 寫回新的值到變量
假設count的初始值為10,當進行並發操作的時候,可能出現線程A和線程B都進行到了1操作,之后又同時進行2操作。A先進行到3操作+1,現在值為11;注意剛才AB獲取到的當前值都是10,所以B執行3操作后,count的值依然是11。這個結果顯然不符合我們的要求。因此這個count++
操作不是線程安全的。
實現線程安全的目標,我們需要引入本篇的主角—— AtomicInteger
。本篇我們介紹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,Unsafe是JDK內部的工具類,主要實現了平台相關的操作。下面內容引自JDK官方文檔:
sun.misc.Unsafe是JDK內部用的工具類。它通過暴露一些Java意義上說“不安全”的功能給Java層代碼,來讓JDK能夠更多的使用Java代碼來實現一些原本是平台相關的、需要使用native語言(例如C或C++)才可以實現的功能。該類不應該在JDK核心類庫之外使用。
Unsafe的具體實現跟本篇的目標關聯不大,你只要知道這段代碼是為了獲取value在堆內存中的偏移量就夠了。
第二個變量是valueOffset,也就是內存偏移量。偏移量在AtomicInteger中很重要,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 的作用是當一個線程修改了共享變量時,另一個線程可以讀取到這個修改后的值。在分析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。
這樣使用CAS就保證了原子操作。其余幾個方法的原理跟這個相同,在此不再過多的解釋。
沒看AtomicInteger 源碼之前,我認為其內部是用synchronized
來實現的原子操作。查閱資料后發現synchronized
會影響性能,因為Java中的synchronized
鎖是獨占鎖,雖然可以實現原子操作,但是這種實現方式的並發性能很差。
四、總結
總結一下,AtomicInteger 中主要實現了整型的原子操作,防止並發情況下出現異常結果,其內部主要依靠JDK 中的unsafe 類操作內存中的數據來實現的。volatile 修飾符保證了value在內存中其他線程可以看到其值得改變。CAS操作保證了AtomicInteger 可以安全的修改value 的值。