17. JUC你瞭解CAS嗎
1. CAS是什麼
CAS的全稱為Compare-And-Swap ,它是一條CPU併發原語,它的功能是判斷記憶體某個位置的值是否為預期值,如果是則更改為新的值,否則繼續比較直到主記憶體和工作記憶體中的值一致為止,整個過程是原子的。
CAS併發原語體現在Java語言中就是sun.misc.Unsafe類中的各個方法。
呼叫Unsafe類中的CAS方法,JVM會幫我們實現CAS彙編指令。
這是一種完全依賴於硬體的功能,通過它實現了原子操作。
再次強調,由於CAS是一種系統原語,原語屬於作業系統用語範疇,是由若干條指令組成,用於完成某個功能的一個過程,並且原語的執行必須是連續的,在執行過程中不允許中斷,也就是說CAS是一條CPU的原子指令,不會造成所謂的資料不一致問題。
2. 比較並交換
- 主實體記憶體中有1個5,此時有兩個執行緒要對其操作
- A、B執行緒都對主實體記憶體的這個值進行了副本拷貝
- A執行緒從主實體記憶體中取走的時候是5,要寫入主實體記憶體時發現主實體記憶體的值還是5,這說明沒有人動過,此時成功將值修改為2019
- B執行緒當它要寫入主實體記憶體,判斷拿走的是否與主實體記憶體一致時,發現主實體記憶體的值已經被修改過了,則修改失敗,主實體記憶體的值保持不變。
public class CASDemo {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(5);
System.out.println(atomicInteger.compareAndSet(5, 2021) + "\t" + atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(5, 2022) + "\t" + atomicInteger.get());
}
}
結論:如果執行緒的期望值與主實體記憶體的真實值一樣,那就修改為更新值,返回true,如果不一樣,則修改失敗,返回false。
3. CAS的底層原理
Unsafe類加自旋
談談你對Unsafe的理解
AtomicInteger.java
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();
// 表示該變數在記憶體中的偏移地址,因為Unsafe就是根據記憶體偏移地址獲取資料的
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
// value用volatile修飾,保證多執行緒之間的可見性
private volatile int value;
//...
}
Unsafe存在於sun.misc包中,是CAS的核心類
由於Java 方法無法直接訪問底層 ,所以需要通過本地(native)方法來訪問
而Unsafe類所有的方法都是native修飾的,它的內部方法可以像C的指標一樣直接操作記憶體
就是直接呼叫作業系統底層資源執行相應任務,相當於Java直接呼叫作業系統資源的中間類,所以Java中CAS的執行依賴於Unsafe類的方法
底層原理詳解
AtomicInteger.java
- this:代表當前物件
- valueOffset:記憶體偏移地址
- 1:需要變動的數量
Unsafe.class
- var1:代表當前物件
- var2:記憶體偏移地址
- var4:需要變動的數量
- var5:是通過var1跟var2找出的主記憶體中真實的值
- AtomicInteger裡面的value原始值為3,即主記憶體中AtomicInteger的value為3,根據JMM模型,執行緒A和執行緒B分別在工作記憶體中持有一份值為3的value的副本
- 執行緒A通過
getIntVolatile(var1,var2)
拿到value值3,這時執行緒A被掛起 - 執行緒B也通過
getIntVolatile(var1,var2)
拿到value值3,此時剛好執行緒B沒有被掛起並執行了compareAndSwapInt
方法,結果比較記憶體中的值也是3,成功修改記憶體值為4,執行緒B打完收工 ,一切OK - 這時執行緒A恢復,執行
compareAndSwapInt
方法比較,發現自己手裡的數值和記憶體中的數字4不一致,說明該值已經被其他執行緒搶先修改了,A執行緒修改失敗,只能重新再來 - 執行緒A重新獲取value值,因為變數value是volatile修飾,所以修改對其它的執行緒是可見的,執行緒A繼續執行
compareAndSwapInt
方法進行比較替換,直到成功
4. CAS缺點
-
迴圈時間長開銷大
在getAndAddInt方法中有個do while,如果CAS失敗,會一直進行嘗試,如果CAS長時間一直不成功,則會給CPU帶來很大的開銷。
-
只能保證一個共享變數的原子操作
對一個共享變數執行操作時,我們可以使用CAS的方式來保證原子性,但是對多個共享變數操作時,迴圈CAS就無法保證操作的原子性了,這個時候就需要用鎖來保證原子性
-
引出來ABA問題?
總結:
- synchronized加鎖,一致性保證,併發性下降
- CAS不加鎖,保證一致性,但是需要多次比較,耗費時間長,開銷較大。