串燒 JavaCAS相關知識

JMM與問題引入

爲啥先說JMM,由於CAS的實現類中維護的變量都被volatile修飾, 這個volatile 是遵循JMM規範(不是百分百遵循,下文會說)實現的保證多線程併發訪問某個變量實現線程安全的手段java

一連串的知識點慢慢縷c++

jmm

首先說什麼是JMM, JMM就是你們所說的java的內存模型, 它是人們在邏輯上作出的劃分, 或者能夠將JMM當成是一種規範, 有哪些規範呢? 以下安全

  1. 可見性: 某一個線程對內存中的變量作出改動後,要求其餘的線程在第一事件內立刻馬獲得通知,在CAS的實現中, 可見性實際上是經過不斷的while循環讀取而獲得的通知, 而不是被動的獲得通知
  2. 原子性: 線程在執行某個操做的時,要麼一塊兒成功,要麼就一塊兒失敗
  3. 有序性: 爲了提升性能, 編譯器處理器會進行指令的重排序, 源碼-> 編譯器優化重排 -> 處理器優化重排 -> 內存系統重排 -> 最終執行的命令

JVM運行的實體是線程, 每個線程在建立以後JVM都會爲其建立一個工做空間, 這個工做空間是每個線程之間的私有空間, 而且任何兩條線程之間的都不能直接訪問到對方的工做空間, 線程之間的通訊,必須經過共享空間來中轉完成多線程

JMM規定全部的變量所有存在主內存中,主內存是一塊共享空間,那麼若是某個線程相對主內存中共享變量作出修改怎麼辦呢? 像下面這樣:併發

  1. 將共享變量的副本拷貝到工做空間中
  2. 對變量進行賦值修改
  3. 將工做空間中的變量寫回到內存中

JMM還規定以下:函數

  • 任何線程在解鎖前必須將工做空間的共享變量當即刷新進內存中
  • 線程在加鎖前必須讀取主內存中的值更新到本身的工做空間中
  • 加鎖和解鎖是同一把鎖

問題引入性能

這時候若是多個線程併發按照上面的三步走去訪問主內存中的共享變量的話就會出現線程安全性的問題, 好比說 如今主內存中的共享變量是c=1, 有AB兩個線程去併發訪問這個c變量, 都想進行c++, 如今A將c拷貝到本身的工做空間進行c++, 因而c=2 , 於此同時線程B也進行c++, c在B的工做空間中=2, AB線程將結果寫回工做空間最終的結果就是2, 而不是咱們預期的3優化

相信怎麼解決你們都知道, 就是使用JUC,中的原子類就能規避這個問題this

而原子類的底層實現使用的就是CAS技術atom


什麼是CAS

CAS(compare and swap) 顧名思義: 比較和交換,在JUC中原子類的底層使用的都是CAS無鎖實現線程安全,是一門很炫的技術

以下面兩行代碼, 先比較再交換, 即: 若是從主內存中讀取到的值爲4就將它更新爲2019

AtomicInteger atomicInteger = new AtomicInteger(4);
        atomicInteger.compareAndSet(4,2019);

跟進AtomicInteger的源碼以下, 底層維護着一個int 類型的 變量, (固然是由於我選擇的原來類是AtomicInteger類型), 而且這個int類型的值被 volatile 修飾

private volatile int value;

    /**
     * Creates a new AtomicInteger with the given initial value.
     *
     * @param initialValue the initial value
     */
    public AtomicInteger(int initialValue) {
        value = initialValue;
    }

什麼是volatile

volatile是JVM提供的輕量的同步機制, 爲何是輕量界別呢? , 剛纔在上面說了JMM規範中提到了三條特性, 而JVM提供的volatile僅僅知足上面的規範中的 2/3, 以下:

  1. 保證可見性
  2. 不保證原子性
  3. 禁止指令重排序

單獨的volatile是不能知足原子性的,即以下代碼在多線程併發訪問的狀況下依然會出現線程安全性問題

private volatile int value;
 
public void add(){
  value++;   
}

那麼JUC的原子類是如何實現的 能夠知足原子性呢? 因而就不得不說本片博文的主角, CAS

CAS源碼跟進

咱們跟進AtomicInteger中的先遞增再獲取的方法 incrementAndGet()

public final int incrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }

經過代碼咱們看到調用了Unsafe類來實現

什麼是Unsafe類?

進入Unsafe類,能夠看到他裏面存在大量的 native方法,這些native方法所有是空方法,

這個unsafe類其實至關於一個後門,他是java去訪問調用系統上 C C++ 函數類庫的方法 以下圖

unsafe

繼續跟進這個方法incrementAndGet() 因而咱們就來到了咱們的主角方法, 關於這個方法卻是不難理解,主要是搞清楚方法中的var12345到底表明什麼就行, 以下代碼+註釋

var1: 上一個方法傳遞進來的: this,即當前對象
var2: 上一個方法傳遞進來的valueOffset, 就是內存地址偏移量
      經過這個內存地址偏移量我能精確的找到要操做的變量在內存中的地址
      
var4: 上一個方法傳遞進來的1, 就是每次增加的值
var5: 經過this和內存地址偏移量讀取出來的當前內存中的目標值
public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

        return var5;
    }

注意它用的是while循環, 相對if(flag){} 這種寫法會多一次判斷, 總體的思路就是 在進行修改以前先進行一次比較,若是讀取到的當前值和預期值是相同的,就自增,不然的話就繼續輪詢修改

小總結

經過上面的過程, 其實就能總結出CAS的底層實現原理

  • volatile
  • 自旋鎖
  • unsafe類

補充: CAS經過Native方法的底層實現,本質上是操做系統層面上的CPU的併發原語,JVM會直接實現出彙編層面的指令,依賴於硬件去實現, 此外, 對於CPU的原語來講, 有兩條特性1,一定連續, 2.不被中斷

CAS的優缺點

優勢:

它的底層咱們看到了經過do-while 實現的自旋鎖來實現, 就省去了在多個線程之間進行切換所帶來的額外的上下文切換的開銷

缺點:

  1. 經過while循環不斷的嘗試獲取, 省去了上下文切換的開銷,可是佔用cpu的資源
  2. CAS只能保證一個共享變量的原子性, 若是存在多個共享變量的話不得不加鎖實現
  3. 存在ABA問題

ABA問題

什麼是ABA問題

咱們這樣玩, 仍是AB兩個線程, 給AtomicInteger賦初始值0

A線程中的代碼以下:

Thread.sleep(3000);
        atomicInteger.compareAndSet(0,2019);

B線程中的代碼以下:

atomicInteger.compareAndSet(0,1);
        atomicInteger.compareAndSet(1,0);

AB線程同時啓動, 雖然最終的結果A線程能成果的將值修改爲2019,,可是它不能感知到在他睡眠過程當中B線程對數據進行過改變, 換句話說就是A線程被B線程欺騙了

ABA問題的解決--- AtomicStampedRefernce.java

帶時間戳的原子引用, 實現的機制就是經過 原子引用+版本號來完成, 每次對指定值的修改相應的版本號會加1, 實例以下

// 0表示初始化, 1表示初始版本號
        AtomicStampedReference<Integer> reference = new AtomicStampedReference<>(0, 1);
        reference.getStamp(); // 獲取版本號
        reference.attemptStamp(1,2); // 期待是1, 若是是1就更新爲2

原子引用

JUC中咱們能夠找到像AtomicInteger這樣已經定義好了實現類, 可是JUC沒有給咱們提供相似這樣 AtomicUser或者 AtomicProduct 這樣自定義類型的原子引用類型啊, 不過java仍然是提供了後門就是 原子引用類型

使用實例:

User user  = getUserById(1);
        AtomicReference<User> userAtomicReference = new AtomicReference<User>();
        user.setUsername("張三");
        userAtomicReference.compareAndSet(user,user);

歡迎關注我, 會繼續更新筆記

相關文章
相關標籤/搜索