Java CAS簡析
什麽是CAS
CAS:Compare and Swap,它是一種原子操作,什麽是原子操作,可以在多線程編程中實現數據交換而不被打斷。是用來更新變量的,當多個線程使用CAS來更新變量時,只有一個線程可以更新變量的值,其他線程都會失敗,失敗的線程不回被掛起,而是重試直到成功為止。
CAS實現方式,有三個變量,內存值V,處理器緩存(預期值)A,更新值B。比較內存值與預期值,相等則說明沒有其他線程操作共享數據,則更新內存值為B;不等說明共享數據V已經被其他線程改動了,放棄更新操作,重新load共享數據,重試之前操作。
CAS的運用
樂觀鎖:其實現機制是基於CAS的,每次不加鎖,假設沒有沖突完成操作,如果有沖突,充實知道成功為止。
Java原子類:AtomicInteger等
源碼分析
一起看一下AtomicInteger是怎麽實現free-lock的。
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 final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
value 是用volatile 修飾的,可以保證get與set的原子性,但無法保證incrementAndGet讀寫改的復合操作。其實際調用的方法是unsafe的getAndAddInt
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
unsafe 通過JNI調用C++ 的一些方法,首先是根據內存地址獲取到value的值,然後調用compareAndSwapInt,true:更新變量成功,false:不成功。compareAndSwapInt經過匯編後最主要的是CMPXCHG指令,該指令是 操作系統級別的原子操作。所以,拋開C++與匯編源碼,可以理解為Java運用了os的原子性來實現無鎖編程。
那操作系統是如何保證CAS原子性呢?在寫操作前會有一個Lock前綴,它會引發兩件事情。
- 將當前處理器緩存行的數據寫回到內存行。
- 這個寫緩存操作會使其他CPU裏的緩存了該內存地址的數據無效。
使用總線鎖保證原子性:
處理器提供一個Lock# 信號,當處理器在總線上輸出此信號時,其他處理的請求將被阻塞,改處理器就可以獨占共享內存。
缺點:對於多核(32,64),其中一個cpu鎖住了總線,其他所有核與內存之間的通信都被鎖住了,開銷太大。
使用緩存鎖保證原子性
直接修改內存,緩存一致性機制來保障操作的原子性。
ABA 問題
一個值原來是A,變成了B,又變成了A,那麽使用CAS進行檢查時會發現它的值沒有變化,其實卻變化了,解決思路是加上版本號。
參考:
- Java 並發編程的藝術
- 用AtomicStampedReference解決ABA問題
- 非阻塞同步算法與CAS(Compare and Swap)無鎖算法
Java CAS簡析