CAS
即Compare And Swap
的縮寫,翻譯成中文就是比較並交換,其做用是讓CPU比較內存中某個值是否和預期的值相同,若是相同則將這個值更新爲新值,不相同則不作更新,也就是CAS是原子性的操做(讀和寫二者同時具備原子性),其實現方式是經過藉助C/C++
調用CPU指令完成的,因此效率很高。
CAS
的原理很簡單,這裏使用一段Java
代碼來描述java
public boolean compareAndSwap(int value, int expect, int update) {
// 若是內存中的值value和指望值expect同樣 則將值更新爲新值update
if (value == expect) {
value = update;
return true;
} else {
return false;
}
}
複製代碼
大體過程是將內存中的值、咱們的指望值、新值交給CPU進行運算,若是內存中的值和咱們的指望值相同則將值更新爲新值,不然不作任何操做。這個過程是在CPU中完成的,這裏很差描述CPU的工做過程,就拿Java代碼來描述了。segmentfault
Java是在Unsafe(sun.misc.Unsafe)
類實現CAS
的操做,而咱們知道Java是沒法直接訪問操做系統底層的API的(緣由是Java的跨平臺性限制了Java不能和操做系統耦合),因此Java並無在Unsafe
類直接實現CAS
的操做,而是經過**JDI(Java Native Interface)**本地調用C/C++
語言來實現CAS
操做的。
Unsafe
有不少個CAS
操做的相關方法,這裏舉例幾個安全
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);
複製代碼
咱們拿public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
進行分析,這個方法是比較內存中的一個值(整型)和咱們的指望值(var4)是否同樣,若是同樣則將內存中的這個值更新爲var5
,參數中的var1
是值所在的對象,var2
是值在對象(var1)中的內存偏移量,參數var1和參數var2是爲了定位出值所在內存的地址。
bash
Unsafe.cpp
true
給開發者,沒有更新則返回false
Unsafe.cpp在這裏發揮的做用有:多線程
Unsafe
傳遞過來的對象引用、偏移量、指望的值和欲更新的新值,根據對象引用和偏移量計算出值的地址,而後將值的地址、指望的值、欲更新的新值傳遞給CPUtrue
給Unsafe.java
,沒有更新則返回false
CPU在這裏發揮的做用:源碼分析
Unsafe.cpp
傳遞過來的地址、指望的值和欲更新的新值,執行指令cmpxchg
,比較地址中的值是否和指望的值同樣,同樣則將值更新爲新的值,不同則不作任何操做Unsafe.cpp
CAS
雖然高效的實現了原子性操做,可是也存在一些缺點,主要表如今如下三個方面。ui
在多線程場景下CAS
會出現ABA
問題,關於ABA問題這裏簡單科普下,例若有2個線程同時對同一個值(初始值爲A)進行CAS操做,這三個線程以下spa
線程1
搶先得到CPU時間片,而線程2
由於其餘緣由阻塞了,線程1
取值與指望的A值比較,發現相等而後將值更新爲B,而後這個時候出現了線程3
,指望值爲B,欲更新的值爲A,線程3取值與指望的值B比較,發現相等則將值更新爲A,此時線程2
從阻塞中恢復,而且得到了CPU時間片,這時候線程2
取值與指望的值A比較,發現相等則將值更新爲B,雖然線程2
也完成了操做,可是線程2
並不知道值已經通過了A->B->A
的變化過程。操作系統
ABA
問題帶來的危害:
小明在提款機,提取了50元,由於提款機問題,有兩個線程,同時把餘額從100變爲50
線程1(提款機):獲取當前值100,指望更新爲50,
線程2(提款機):獲取當前值100,指望更新爲50,
線程1成功執行,線程2某種緣由block了,這時,某人給小明匯款50
線程3(默認):獲取當前值50,指望更新爲100,
這時候線程3成功執行,餘額變爲100,
線程2從Block中恢復,獲取到的也是100,compare以後,繼續更新餘額爲50!!!
此時能夠看到,實際餘額應該爲100(100-50+50),可是實際上變爲了50(100-50+50-50)這就是ABA問題帶來的成功提交。線程
解決方法: 在變量前面加上版本號,每次變量更新的時候變量的版本號都+1
,即A->B->A
就變成了1A->2B->3A
。
若是CAS
操做失敗,就須要循環進行CAS
操做(循環同時將指望值更新爲最新的),若是長時間都不成功的話,那麼會形成CPU極大的開銷。
這種循環也稱爲自旋
解決方法: 限制自旋次數,防止進入死循環。
CAS
的原子操做只能針對一個共享變量。
解決方法: 若是須要對多個共享變量進行操做,可使用加鎖方式(悲觀鎖)保證原子性,或者能夠把多個共享變量合併成一個共享變量進行CAS
操做。
咱們知道CAS
操做並不會鎖住共享變量,也就是一種非阻塞的同步機制,CAS
就是樂觀鎖的實現。
Java
利用CAS
的樂觀鎖、原子性的特性高效解決了多線程的安全性問題,例如JDK1.8中的集合類ConcurrentHashMap
、關鍵字volatile
、ReentrantLock
等。
JAVA CAS原理深度分析
Java CAS 原理分析
什麼是ABA問題?
原文地址:ddnd.cn/2019/03/13/…