Java CAS底層原理
Java CAS底層原理,這一篇就夠了!!!
CAS全稱(Conmpare And Swap)比較並交換,是一種用於在多線程環境下實現同步功能的機制。CAS 操作包含三個操作數 – 內存地址、預期值和新值。CAS 的實現邏輯是將內存地址的數值與預期數值想比較,若相等,則將內存位置處的值替換爲新值。若不相等,則不做任何操作。
JAVA中CAS是通過自旋操作完成賦值,若值不相等再更新預期值、重新計算新值,接着進行CAS操作,直到成功爲止。底層是JVM調用操作系統原語指令unsafe
,並由CPU完成原子操作,你要知道併發/多線程環境下如果CPU沒有原子操作我們是無法完成。
- JAVA1.5開始引入了CAS,主要代碼都放在JUC的atomic包下,如下圖:
- JUC包下源碼如下:
- 每一個操作都是調用
unsafe
方法實現結果。這是java自旋完成CAS源碼:
CAS優點
- 沒有引用鎖的概念,併發量不高情況下提高效率
- 減少線程上下文切換
CAS缺點
- cpu開銷大,在高併發下,許多線程,更新一變量,多次更新不成功,循環反覆,給cpu帶來大量壓力。
- 只是一個變量的原子性操作,不能保證代碼塊的原子性。
- ABA問題
ABA問題
CAS帶來最大問題就是ABA問題。有A、B兩個線程,A線程運行10s,B線程運行2s,兩個線程同一時間開始運行都修改同一變量m,假設m初始值爲5,B線程修改m值5改爲10,再修改m值10改爲5。此時A線程修改m值5改爲6,修改成功,滿足CAS操作原理,而A線程認爲m一直都是5,它並不知道m的值已經被修改過。
解決該問題通過添加版本號來解決,每次修改都帶着本次修改的版本號,發現版本不是當前版本此修改失敗,否則修改成功。如果自旋同理重新獲得版本號計算舊值、新值等。
public class ABA {
//ABA問題產生與解決
static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);
static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1);
public static void main(String[] args) {
//==========ABA問題的產生==========
System.out.println("==========ABA問題的產生==========");
new Thread(() -> {
atomicReference.compareAndSet(100, 101);
atomicReference.compareAndSet(101, 100);
}, "t1").start();
new Thread(() -> {
//保證t1線程完成
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicReference.compareAndSet(100, 2019) + "\t" + atomicReference.get());
}, "t2").start();
//暫停一會
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//==========ABA問題的解決==========
System.out.println("==========ABA問題的解決==========");
new Thread(() -> {
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + "\t第一次版本號:" + stamp);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
atomicStampedReference.compareAndSet(100, 101, stamp, stamp + 1);
System.out.println(Thread.currentThread().getName() + "\t第二次版本號:" + atomicStampedReference.getStamp());
atomicStampedReference.compareAndSet(101, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
System.out.println(Thread.currentThread().getName() + "\t第三次版本號:" + atomicStampedReference.getStamp());
}, "t3").start();
new Thread(() -> {
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + "\t第一次版本號:" + stamp);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean result = atomicStampedReference.compareAndSet(100, 101, stamp, stamp + 1);
System.out.println(Thread.currentThread().getName() + "\t修改成功否" + result + "\t當前最前版本號:" + atomicStampedReference.getStamp());
System.out.println(Thread.currentThread().getName() + "\t當前實際值:" + atomicStampedReference.getReference());
}, "t4").start();
}
}
- 運行結果: