點擊上方 Java後端技術棧,選擇 設爲星標
回覆面試,獲取乾貨
來源:http://8nn.co/a4Qc
無鎖
“無鎖”即不用鎖保證程序的併發。無鎖採用CAS(compare and swap)算法來處理線程衝突。
CAS
CAS的全稱爲Compare-And-Swap,直譯就是對比交換。是一條CPU的原子指令,其作用是讓CPU先進行比較兩個值是否相等,然後原子地更新某個位置的值,經過調查發現,其實現方式是基於硬件平臺的彙編指令,就是說CAS是靠硬件實現的,JVM只是封裝了彙編調用,那些AtomicInteger類便是使用了這些封裝後的接口。
CAS包含3個參數CAS(V,E,N)。V表示要更新的變量, E表示預期值, N表示新值。僅當V值等於E值時, 纔會將V的值設爲N, 如果V值和E值不同, 則說明已經有其他線程做了更新, 則當前線程什麼都不做。最後, CAS返回當前V的真實值。CAS操作是抱着樂觀的態度進行的, 它總是認爲自己可以成功完成操作。當多個線程同時使用CAS操作一個變量時, 只有一個會勝出, 併成功更新,其餘均會失敗。失敗的線程不會被掛起,僅是被告知失敗, 並且允許再次嘗試,當然也允許失敗的線程放棄操作。基於這樣的原理, CAS操作即時沒有鎖,也可以發現其他線程對當前線程的干擾, 並進行恰當的處理。
CAS操作是原子性的,所以多線程併發使用CAS更新數據時,可以不使用鎖。JDK中大量使用了CAS來更新數據而防止加鎖(synchronized 重量級鎖)來保持原子更新。
java.util.concurrent包都中的實現類都是基於volatile和CAS來實現的。尤其java.util.concurrent.atomic包下的原子類。
簡單介紹下volatile特性(具體可以參見另一篇筆記"Java併發之volatile"):
內存可見性(當一個線程修改volatile變量的值時,另一個線程就可以實時看到此變量的更新值)
禁止指令重排(volatile變量之前的變量執行先於volatile變量執行,volatile之後的變量執行在volatile變量之後)
AtomicInteger 源碼解析
public class AtomicInteger extends Number implements java.io.Serializable {
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
//用於獲取value字段相對當前對象的“起始地址”的偏移量
valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
private volatile int value;
//返回當前值
public final int get() {
return value;
}
//遞增加detla
public final int getAndAdd(int delta) {
//三個參數,1、當前的實例 2、value實例變量的偏移量 3、當前value要加上的數(value+delta)。
return unsafe.getAndAddInt(this, valueOffset, delta);
}
//遞增加1
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
...
}
可以看到 AtomicInteger 底層用的是volatile的變量和CAS來進行更改數據的。volatile保證線程的可見性,多線程併發時,一個線程修改數據,可以保證其它線程立馬看到修改後的值
CAS 保證數據更新的原子性。
Unsafe源碼解析
Unsafe 類中代碼反編譯出來的結果如下:
public final int getAndAddInt(Object paramObject, long paramLong, int paramInt)
{
int i;
do
i = getIntVolatile(paramObject, paramLong);
while (!compareAndSwapInt(paramObject, paramLong, i, i + paramInt));
return i;
}
public final long getAndAddLong(Object paramObject, long paramLong1, long paramLong2)
{
long l;
do
l = getLongVolatile(paramObject, paramLong1);
while (!compareAndSwapLong(paramObject, paramLong1, l, l + paramLong2));
return l;
}
public final int getAndSetInt(Object paramObject, long paramLong, int paramInt)
{
int i;
do
i = getIntVolatile(paramObject, paramLong);
while (!compareAndSwapInt(paramObject, paramLong, i, paramInt));
return i;
}
public final long getAndSetLong(Object paramObject, long paramLong1, long paramLong2)
{
long l;
do
l = getLongVolatile(paramObject, paramLong1);
while (!compareAndSwapLong(paramObject, paramLong1, l, paramLong2));
return l;
}
public final Object getAndSetObject(Object paramObject1, long paramLong, Object paramObject2)
{
Object localObject;
do
localObject = getObjectVolatile(paramObject1, paramLong);
while (!compareAndSwapObject(paramObject1, paramLong, localObject, paramObject2));
return localObject;
}
從源碼中發現,內部使用自旋的方式進行CAS更新(while循環進行CAS更新,如果更新失敗,則循環再次重試)。
又從Unsafe類中發現,原子操作其實只支持下面三個方法:
public final native boolean compareAndSwapObject(Object paramObject1, long paramLong, Object paramObject2, Object paramObject3);
public final native boolean compareAndSwapInt(Object paramObject, long paramLong, int paramInt1, int paramInt2);
public final native boolean compareAndSwapLong(Object paramObject, long paramLong1, long paramLong2, long paramLong3);
Unsafe只提供了3種CAS方法:compareAndSwapObject、compareAndSwapInt和compareAndSwapLong。都是native方法。
native方法中使用了CPU原子指令:cmpxchg。它的作用是“比較並交換”。
如:CMPXCHG r/m,r 將累加器AL/AX/EAX/RAX中的值與首操作數(目的操作數)比較,如果相等,第2操作數(源操作數)的值裝載到首操作數,zf置1。如果不等, 首操作數的值裝載到AL/AX/EAX/RAX並將zf清0 該指令只能用於486及其後繼機型。第2操作數(源操作數)只能用8位、16位或32位寄存器。第1操作數(目地操作數)則可用寄存器或任一種存儲器尋址方式。
AtomicBoolean 源碼解析
public class AtomicBoolean implements java.io.Serializable {
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicBoolean.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
private volatile int value;
public AtomicBoolean(boolean initialValue) {
value = initialValue ? 1 : 0;
}
public final boolean compareAndSet(boolean expect, boolean update) {
int e = expect ? 1 : 0;
int u = update ? 1 : 0;
return unsafe.compareAndSwapInt(this, valueOffset, e, u);
}
...
}
從AtomicBoolean源碼,發現他底層也是使用volatile類型的int 變量,跟AtomicInteger 實現方式一樣,只不過是把Boolean轉換成 0和1進行操作。
所以原子更新char、float和double變量也可以轉換成int 或long來實現CAS的操作。
CAS缺點
ABA問題。因爲CAS需要在操作值的時候檢查下值有沒有發生變化,如果沒有發生變化則更新,但是如果一個值原來是A,變成了B,又變成了A,那麼使用CAS進行檢查時會發現它的值沒有發生變化,但是實際上卻變化了。ABA問題的解決思路就是使用版本號。在變量前面追加上版本號,每次變量更新的時候把版本號加一,那麼A-B-A 就會變成1A-2B-3A。從Java1.5開始JDK的atomic包裏提供了一個類AtomicStampedReference來解決ABA問題。這個類的compareAndSet方法作用是首先檢查當前引用是否等於預期引用,並且當前標誌是否等於預期標誌,如果全部相等,則以原子方式將該引用和該標誌的值設置爲給定的更新值。
循環時間長開銷大。自旋CAS如果長時間不成功,會給CPU帶來非常大的執行開銷。
總結
Atomic使用volatile保證可見性和有序性,使用unsafe類下面的CAS方法保證原子性。
推薦閱讀
目前10000+ 人已關注加入我們