「Java」手把手理解CAS實現原理
- 先來看看概念,【CAS】 全稱“CompareAndSwap”,中文翻譯即“比較並替換”。
定義:CAS操作包含三個運算元 —— 記憶體位置(V),期望值(A),和新值(B)。
如果記憶體位置的值與期望值匹配,那麼處理器會自動將該位置值更新為新值。否則,
處理器不作任何操作。無論哪種情況,它都會在CAS指令之前返回該位置的值。
(CAS在一些特殊情況下,僅返回CAS是否成功,而不提去當前值)CAS有效說明了
“我認為【位置V】應該包含【值A】:如果包含【值A】,則將【新值B】放到這個位置;
否則,不要更改該位置的值,只告訴我這個位置現在的值即可”。
- 怎麼使用JDK提供CAS支援?
Java中提供了對CAS操作的支援,具體在【sun.misc.unsafe】類中(官方不建議直接使用)
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
引數var1:表示要操作的物件
引數var2:表示要操作物件中屬性地址的偏移量
引數var4:表示需要修改資料的期望的值
引數var5:表示需要修改為的新值
- 此處描述一下 偏移量 的概念?
這裡的偏移量就像我們【new】一個物件,物件的地址就是【0x001】,那麼value的地址就是【0x002 = 0x001 + 1】,
【+1】就是偏移量。
- CAS的實現原理是什麼?
CAS通過呼叫JNI的程式碼實現(JNI:Java Native Interface),允許java呼叫其他語言,
而【compareAndSwapXXX】系列的方法就是藉助“C語言”來呼叫cpu底層指令實現的。
以常用的【Intel x86】平臺來說,最終對映到cpu的指令為【cmpxchg】(compareAndChange),
這是一個原子指令,cpu執行此命令時,實現比較替換操作。
- 那麼問題來了,現在計算機動不動就上百核,【cmpxchg】怎麼保證多核下的執行緒安全?
系統底層進行CAS操作時,會判斷當前系統是否為多核系統,如果是,就給【匯流排】加鎖,
只有一個執行緒對匯流排加鎖成功, 加鎖成功之後會執行CAS操作,也就是說CAS的原子性是平臺級別的。
- 那麼問題又來了,CAS這麼流批,就不會有什麼問題麼?
1》高併發下,其他執行緒會一直處於自旋阻塞狀態
2》ABA問題(重要)
- 什麼是ABA問題呢?
CAS需要在操作值的時候,檢查下值有沒有發生變化,如果沒有發生變化則更新,
但是可能會有這樣一個情況,如果一個值原來是A,在CAS方法執行之前,被其他執行緒修改為了B,然後又修改回成A,
此時CAS方法執行之前,檢查的時候發現它的值並沒有發生變化,但實際卻變化了,這就是【CAS的ABA】問題。
- 話不多說,我們這裡用程式碼來模擬一下ABA問題:
public class CasABADemo1 {
private static AtomicInteger count = new AtomicInteger(0);
public static void main(String[] args) {
System.out.println("mainThread 當前count值為: " + count.get());
Thread mainThread = new Thread(() -> {
try {
int expectCount = count.get();
int updateCount = expectCount + 1;
System.out.println("mainThread 期望值:" + expectCount + ", 修改值:" + updateCount);
Thread.sleep(2000);//休眠2000s ,釋放cpu
boolean result = count.compareAndSet(expectCount, updateCount);
System.out.println("mainThread 修改count : " + result);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread otherThread = new Thread(() -> {
try {
Thread.sleep(20);//確保主執行緒先獲取到cpu資源
} catch (InterruptedException e) {
e.printStackTrace();
}
count.incrementAndGet();
System.out.println("其他執行緒先修改 count 為:" + count.get());
count.decrementAndGet();
System.out.println("其他執行緒又修改 count 為:" + count.get());
});
mainThread.start();
otherThread.start();
}
}
結果:
mainThread 當前count值為: 0
mainThread 期望值:0, 修改值:1
其他執行緒先修改 count 為:1
其他執行緒又修改 count 為:0
mainThread 修改count : true
最後結果可以看出【mainThread】修改成功,但是【mainThread】獲取到的【expectCount】雖然也是1,但已經不是曾經的【expectCount】。
- 如何解決ABA問題呢?
解決ABA最簡單的方案就是給值加一個版本號,每次值變化,都會修改他的版本號,
CAS操作時都去對比次版本號。
- java中提供了一種版本號控制的方法,可以解決ABA問題:
public boolean compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp)
- 我們對上述程式碼改造一下,再看看結果:
public class CasABADemo2 {
private static AtomicStampedReference<Integer> count = new AtomicStampedReference<>(0, 1);
public static void main(String[] args) {
System.out.println("mainThread 當前count值為: " + count.getReference() + ",版本號為:" + count.getStamp());
Thread mainThread = new Thread(() -> {
try {
int expectStamp = count.getStamp();
int updateStamp = expectStamp + 1;
int expectCount = count.getReference();
int updateCount = expectCount + 1;
System.out.println("mainThread 期望值:" + expectCount + ", 修改值:" + updateCount);
Thread.sleep(2000);//休眠2000s ,釋放cpu
boolean result = count.compareAndSet(expectCount, updateCount, expectStamp, updateStamp);
System.out.println("mainThread 修改count : " + result);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread otherThread = new Thread(() -> {
try {
Thread.sleep(20);//確保主執行緒先獲取到cpu資源
} catch (InterruptedException e) {
e.printStackTrace();
}
count.compareAndSet(count.getReference(), count.getReference() + 1, count.getStamp(), count.getStamp() + 1);
System.out.println("其他執行緒先修改 count 為:" + count.getReference() + " ,版本號:" + count.getStamp());
count.compareAndSet(count.getReference(), count.getReference() - 1, count.getStamp(), count.getStamp() + 1);
System.out.println("其他執行緒又修改 count 為:" + count.getReference() + " ,版本號:" + count.getStamp());
});
mainThread.start();
otherThread.start();
}
}
結果:
mainThread 當前count值為: 0,版本號為:1
mainThread 期望值:0, 修改值:1
其他執行緒先修改 count 為:1 ,版本號:2
其他執行緒又修改 count 為:0 ,版本號:3
mainThread 修改count : false
可見新增版本號可以完美的解決ABA問題!
相關文章
- 【Java】手把手理解CAS實現原理Java
- Java CAS 原理剖析Java
- Java併發之CAS與原子類實現原理講解Java
- Java CAS 原理詳解Java
- 淺析CAS操作與JAVA實現Java
- 【面試題】能聊聊你對CAS的理解以及其底層實現原理可以嗎?面試題
- 理解 Block 實現原理BloC
- 深入理解Java中的HashMap的實現原理JavaHashMap
- 理解CAS
- CAS原理
- 【Java面試題】之CAS原理深度分析Java面試題
- Java ConcurrentHashMap 高併發安全實現原理解析JavaHashMap
- CAS實現單點登入SSO執行原理探究
- 深入理解Java中的底層阻塞原理及實現Java
- 深入理解Java的垃圾回收機制(GC)實現原理JavaGC
- 詳解鎖原理,synchronized、volatile+cas底層實現synchronized
- 夯實Java基礎系列8:深入理解Java內部類及其實現原理Java
- 夯實Java基礎系列18:深入理解Java內部類及其實現原理Java
- JAVA AQS 實現原理JavaAQS
- 七、真正的技術——CAS操作原理、實現、底層原始碼原始碼
- 前端路由原理解析和實現前端路由
- 理解比特幣(4)——實現原理比特幣
- 徹底理解閉包實現原理
- 如何理解CDN?說說實現原理?
- Java併發/多執行緒-CAS原理分析Java執行緒
- 【乾貨理解】理解javascript中實現MVC的原理JavaScriptMVC
- CAS原理深度解析
- 手把手教你用Java實現AOPJava
- 深入理解ReentrantLock的實現原理ReentrantLock
- Android SharedPreferences 實現原理解析Android
- CSS實現元素居中原理解析CSS
- 理解Laravel中介軟體核心實現原理Laravel
- 快速理解容器技術的實現原理
- 深入理解Vue的watch實現原理及其實現方式Vue
- Redis CAS樂觀鎖實現Redis
- Java高階程式設計——MySQL索引實現及優化原理解析Java程式設計MySql索引優化
- CAS實現單點登入SSO執行原理探究(終於明白了)
- Java中HashMap的實現原理JavaHashMap