Compare And Swap底層原理及代碼示例詳解
概念
CAS的全稱是Compare-And-Swap,它是cpu并發(fā)原語
它的功能是判斷內存某個位置的值是否為預期值。如果是則更改為新的值,這個過程是原子的
CAS并發(fā)原語體現(xiàn)在java語言中就是sun.misc.Unsafe類的各個方法。調用UnSafe類中的CAS方法,JVM會幫我們實現(xiàn)出CAS匯編指令,這是一種完全依賴于硬件的功能,通過它實現(xiàn)了原子操作,再次強調,由于CAS是一種系統(tǒng)原語,原語屬于操作系統(tǒng)用于范疇,是由若干條指令組成,用于完成某個功能的一個過程,并且原語的執(zhí)行必須是連續(xù)的,在執(zhí)行過程中不允許被中斷,也就是說CAS是一條CPU的原子指令,不會造成所謂的數(shù)據(jù)不一致的問題,也就是說CAS是線程安全的。
代碼使用
首先使用AtomicInteger創(chuàng)建了一個實例,并初始化為5
// 創(chuàng)建一個原子類
AtomicInteger atomicInteger= new AtomicInteger(5)
然后調用CAS方法,企圖更新成2019,這里有兩個參數(shù),一個是5,表示期望值,第二個就是我們要更新的值
atomicInteger.compareAndSet(5,2019)
然后再次使用了一個方法,同樣將值改為1024
atomicInteger.compareAndSet(5,1024)
完整代碼如下:
public class CASDemo { public static void main(String[] args) { // 創(chuàng)建一個原子類 AtomicInteger atomicInteger = new AtomicInteger(5); /** * 一個是期望值,一個是更新值,但期望值和原來的值相同時,才能夠更改 * 假設三秒前,我拿的是5,也就是expect為5,然后我需要更新成 2019 */ System.out.println(atomicInteger.compareAndSet(5, 2019) + "\t current data: " + atomicInteger.get()); System.out.println(atomicInteger.compareAndSet(5, 1024) + "\t current data: " + atomicInteger.get()); } }
上面代碼的執(zhí)行結果為:
這是因為我們執(zhí)行第一個的時候,期望值和原本值是滿足的,因此修改成功,但是第二次后,主內存的值已經改成了2019,不滿足期望值,因此返回了false,本次寫入失敗
這個就類似于SVN或者Git的版本號,如果沒有人更改過,就能夠正常提交,否者需要先將代碼pull下來,合并代碼后,然后提交
CAS底層原理
首先我們先看看atomicInteger.getAndIncrement()方法的源碼
從這里能夠看到,底層又調用了一個unsafe類的getAndAddInt方法
unsafe類
Unsafe是CAS的核心類,由于Java方法無法直接訪問底層系統(tǒng),需要通過本地(Native)方法來訪問,Unsafe相當于一個后門,基于該類可以直接操作特定的內存數(shù)據(jù),Unsafe類存在sun.misc包中,其內部方法操作可以像C指針一樣直接操作內存,因為Java中的CAS操作的執(zhí)行依賴于Unsafe類的方法。
注意Unsafe類的所有方法都是native修飾的,也就是說unsafe類中的方法都直接調用操作系統(tǒng)底層資源執(zhí)行相應的任務
為什么Atomic修飾的包裝類,能夠保證原子性,依靠的就是底層的unsafe類
變量valueOffset
表示該變量值在內存中的偏移地址,因為Unsafe就是根據(jù)內存偏移地址獲取數(shù)據(jù)的
從這里我們可以看到,通過valueOffset,直接通過內存地址,獲取到值,然后進行加1操作
變量value用volatile修飾
保證了多線程之間的內存可見性
var5:就是我們從主內存中拷貝到工作內存中的值
那么操作的時候,需要比較工作內存中的值,和主內存中的值進行比較
假設執(zhí)行 compareAndSwapInt返回false,那么就一直執(zhí)行 while方法,直到期望的值和真實值一樣
- val1:AtomicInteger對象本身
- var2:該對象值得引用地址
- var4:需要變動的數(shù)量
- var5:用var1和var2找到的內存中的真實值
- 用該對象當前的值與var5比較
- 如果相同,更新var5 + var4 并返回true
- 如果不同,繼續(xù)取值然后再比較,直到更新完成
這里沒有用synchronized,而用CAS,這樣提高了并發(fā)性,也能夠實現(xiàn)一致性,是因為每個線程進來后,進入的do while循環(huán),然后不斷的獲取內存中的值,判斷是否為最新,然后在進行更新操作。
假設線程A和線程B同時執(zhí)行getAndInt操作(分別跑在不同的CPU上)
- tomicInteger里面的value原始值為3,即主內存中AtomicInteger的 value 為3,根據(jù)JMM模型,線程A和線程B各自持有一份價值為3的副本,分別存儲在各自的工作內存
- 線程A通過getIntVolatile(var1 , var2) 拿到value值3,這是線程A被掛起(該線程失去CPU執(zhí)行權)
- 線程B也通過getIntVolatile(var1, var2)方法獲取到value值也是3,此時剛好線程B沒有被掛起,并執(zhí)行了compareAndSwapInt方法,比較內存的值也是3,成功修改內存值為4,線程B打完收工,一切OK
- 這是線程A恢復,執(zhí)行CAS方法,比較發(fā)現(xiàn)自己手里的數(shù)字3和主內存中的數(shù)字4不一致,說明該值已經被其它線程搶先一步修改過了,那么A線程本次修改失敗,只能夠重新讀取后在來一遍了,也就是在執(zhí)行do while
- 線程A重新獲取value值,因為變量value被volatile修飾,所以其它線程對它的修改,線程A總能夠看到,線程A繼續(xù)執(zhí)行compareAndSwapInt進行比較替換,直到成功。
Unsafe類 + CAS思想: 也就是自旋,自我旋轉底層匯編
Unsafe類中的compareAndSwapInt是一個本地方法,該方法的實現(xiàn)位于unsafe.cpp中
- 先想辦法拿到變量value在內存中的地址
- 通過Atomic::cmpxchg實現(xiàn)比較替換,其中參數(shù)X是即將更新的值,參數(shù)e是原內存的值
CAS不加鎖,保證一次性,但是需要多次比較
- 循環(huán)時間長,開銷大(因為執(zhí)行的是do while,如果比較不成功一直在循環(huán),最差的情況,就是某個線程一直取到的值和預期值都不一樣,這樣就會無限循環(huán))
- 只能保證一個共享變量的原子操作
- 當對一個共享變量執(zhí)行操作時,我們可以通過循環(huán)CAS的方式來保證原子操作
- 但是對于多個共享變量操作時,循環(huán)CAS就無法保證操作的原子性,這個時候只能用鎖來保證原子性
- 引出來ABA問題?
總結CAS
CAS是compareAndSwap,比較當前工作內存中的值和主物理內存中的值,如果相同則執(zhí)行規(guī)定操作,否者繼續(xù)比較直到主內存和工作內存的值一致為止
CAS應用
CAS有3個操作數(shù),內存值V,舊的預期值A,要修改的更新值B。當且僅當預期值A和內存值V相同時,將內存值V修改為B,否者什么都不做
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
- 解決java.lang.ClassCastException的java類型轉換異常的問題
- 淺談java switch如果case后面沒有break,會出現(xiàn)什么情況?
- Java CAS基本實現(xiàn)原理代碼實例解析
- Element Cascader 級聯(lián)選擇器的使用示例
- Python基于字典實現(xiàn)switch case函數(shù)調用
- 淺談keras中的后端backend及其相關函數(shù)(K.prod,K.cast)
- springboot集成CAS實現(xiàn)單點登錄的示例代碼
- Python Switch Case三種實現(xiàn)方法代碼實例
- 詳解shell腳本中的case條件語句介紹和使用案例
- Android Broadcast 和 BroadcastReceiver的權限限制方式
相關文章
springboot如何配置上傳文件的maxRequestSize
這篇文章主要介紹了springboot如何配置上傳文件的maxRequestSize,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-03-03基于Springboot+Junit+Mockito做單元測試的示例
本篇文章主要介紹了基于Springboot+Junit+Mockito做單元測試的示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-02-02Java多線程中的ReentrantLock可中斷鎖詳細解讀
這篇文章主要介紹了Java多線程中的ReentrantLock可中斷鎖詳細解讀,ReentrantLock中的lockInterruptibly()方法使得線程可以在被阻塞時響應中斷,比如一個線程t1通過lockInterruptibly()方法獲取到一個可重入鎖,并執(zhí)行一個長時間的任務,需要的朋友可以參考下2023-12-12Spring CGLlB動態(tài)代理實現(xiàn)過程解析
這篇文章主要介紹了Spring CGLlB動態(tài)代理實現(xiàn)過程解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2019-10-10使用Spring Data Redis實現(xiàn)數(shù)據(jù)緩存的方法
目前在系統(tǒng)架構設計中使用Redis實現(xiàn)緩存,這篇文章主要介紹了使用Spring Data Redis實現(xiàn)數(shù)據(jù)緩存的方法,具有一定的參考價值,需要的朋友可以參考下2018-11-11Docker 解決openjdk容器里無法使用JDK的jmap等命令問題
這篇文章主要介紹了Docker 解決openjdk容器里無法使用JDK的jmap等命令問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-12-12