java CAS原理
一、CAS簡介
1. CAS是什麼?
CAS全稱是Compare and Swap,即比較並交換,是通過原子指令來實現多執行緒的同步功能,將獲取儲存在記憶體地址的原值和指定的記憶體地址進行比較,只有當他們相等時,交換指定的預期值和記憶體中的值,這個操作是原子操作,若不相等,則重新獲取儲存在記憶體地址的原值。
2. CAS的流程
CAS是一種無鎖演演算法,有3個關鍵運算元,記憶體地址,舊的記憶體中預期值,要更新的新值,當記憶體值和舊的記憶體中預期值相等時,將記憶體中的值更新為新值。
3.樂觀鎖與悲觀鎖
CAS屬於樂觀鎖,樂觀鎖就是每次不加鎖而是假設沒有衝突而去完成某項操作,如果因為衝突失敗就重試,直到成功為止。
synchronized是悲觀鎖,被一個執行緒拿到鎖之後,其他執行緒必須等待該執行緒釋放鎖,效能較差
二、AtomicInteger程式碼演示
在java中,a++不是原子操作,一個簡單的a++操作涉及到三個操作,獲取變數a的記憶體值,將變數a+1,將新值寫入記憶體,這裡涉及到了兩次記憶體訪問,如果在多執行緒環境下,那麼會出現併發安全問題。
AtomicInteger是一個原子操作類,內部採用的就是CAS無鎖演演算法。
這裡我們分析一下它的內部實現。
AtomicInteger atomicInteger = new AtomicInteger(0);
atomicInteger.getAndSet(1);
複製程式碼
這裡的靜態程式碼塊AtomicInteger物件初始化之前就執行,獲取AtomicInteger物件value欄位相對AtomicInteger物件的”起始地址”的偏移量,Java物件在記憶體中儲存的佈局可以分為三塊區域:物件頭(Header)、例項資料(Instance Data)和對齊填充(Padding),”起始地址”的偏移量即是物件頭的偏移量。
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
複製程式碼
public final int getAndSet(int newValue) {
return unsafe.getAndSetInt(this,valueOffset,newValue);
}
複製程式碼
每次通過記憶體地址(var2)先從記憶體中獲取記憶體中原值(var5),再迴圈將記憶體中的原值(var5)與給定記憶體地址(var2)相比較,如果相等則更新指定預期值(var4),如果不相等則再重試直到成功為止,最後返回舊的記憶體原值var5。
//var1為AtomicInteger物件,var2為記憶體地址值,var4為指定的預期值
public final int getAndSetInt(Object var1,long var2,int var4) {
int var5;
do {
//unsafe.getIntVolatile呼叫本地方法獲取記憶體中值
var5 = this.getIntVolatile(var1,var2);
} while(!this.compareAndSwapInt(var1,var2,var5,var4));
return var5;
}
複製程式碼
三、弊端
1. ABA問題
CAS在操作的時候會檢查變數的值是否被更改過,如果沒有則更新值,但是帶來一個問題,最開始的值是A,接著變成B,最後又變成了A。經過檢查這個值確實沒有修改過,因為最後的值還是A,但是實際上這個值確實已經被修改過了。為瞭解決這個問題,在每次進行操作的時候加上一個版本號,每次操作的就是兩個值,一個版本號和某個值,A——>B——>A問題就變成了1A——>2B——>3A。在jdk中提供了AtomicStampedReference類解決ABA問題,用Pair這個內部類實現,包含兩個屬性,分別代表版本號和引用,在compareAndSet中先對當前引用進行檢查,再對版本號標誌進行檢查,只有全部相等才更新值。
2. 只能保證一個共享變數的原子操作
多個共享變數操作時,迴圈CAS就無法保證操作的原子性,這個時候就可以用鎖。從java1.5開始,JDK提供了AtomicReference類來保證引用物件之間的原子性,就可以把多個變數放在一個物件裡來進行CAS操作。
3. 迴圈時間長CPU開銷較大
在併發量比較高的情況下,如果許多執行緒反覆嘗試更新某一個變數,卻又一直更新不成功,迴圈往復,會給CPU帶來很大的壓力。
作者:陶章好
連結:juejin.im/post/5d63ea…
來源:掘金
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。