java CAS原理分析和ABA問題的產生和解決方案

本文會從java的源碼對CAS算法進行分析,分析出CAS底層實現和併發包的原子類操作用CAS而不用synchronized,此外還會分析CAS的缺點和ABA問題的解決方案,希望能夠對大家有所幫助

1.什麼是CAS

CAS 全稱是 compare and swap,是一種用於在多線程環境下實現同步功能的機制。CAS 操作包含三個操作數 -- 內存位置、預期數值和新值。CAS 的實現邏輯是將內存位置處的數值與預期數值想比較,若相等,則將內存位置處的值替換爲新值。若不相等,則不做任何操作。(更深層次的理解需要依靠彙編進行解釋)

2.通過一個多線程累加一個變量案例來引入CAS

/**
 * 對一個變量進行累加
 */
public class CASDemo {

  
    public void add(){
        nummber++;
    }
    public void atomicAdd(){
        atomicInteger.getAndIncrement();
    }

    public static void main(String[] args) {

        ShareData shareData = new ShareData();
        for (int i = 0; i <20 ; i++) {
            new Thread(()->{
                for (int j = 0; j < 1000; j++) {
                   shareData.add();
                   shareData.atomicAdd();
                }
            },String.valueOf(i)).start();
        }
        while (Thread.activeCount()>2){
            Thread.yield(); //會使這個線程由正在運行的running狀態轉變爲等待cpu時間片的runable狀態。
        }
        System.out.println(shareData.nummber);  //結果小於等於20000
       System.out.println(shareData.atomicInteger.get()); //結果的每次都等於20000
    }
}
class ShareData{
    volatile int nummber =0;
    public void add(){
        nummber++;
    }

    AtomicInteger atomicInteger = new AtomicInteger(0);
    public void atomicAdd(){
        atomicInteger.getAndIncrement();
    }
}
通過這個案例,我們能夠發現,對於一個int型的number就行++操作是線程不安全的,想詳細瞭解,請看我的volatile 這篇文章鏈接如下:https://blog.csdn.net/oldshaui/article/details/89342858 ,但是使用JUC下面的AtomicInteger這個類就不會有問題,下面我們來看下AtomicInteger類的底層實現

我們來主要分析下getAndAddInt這個方法(白話分析):首先會執行do裏面的數據,根據當前對象和內存地址,取到主存裏面的值,進入while循環,裏面的compareAndSwapInt是一個Native方法,由C++編寫的,利用JNI調用的,這個就是CAS算法的核心,大致可以理解爲,根據當前對象和內存地址,拿到主存的值,和當前線程的存儲的值進行比較,如果一樣,就進行更新,返回true,取反爲false,跳出while循環,否則爲false,取反爲true,一直在while循環等待

爲什麼atomicInteger底層用CAS,而不用synchronize

CAS算法沒有加鎖,也可以保證線程安全,可以保證併發,synchronize只能保證線程安全,不能保證併發,加鎖就會讓程序串行化

CAS的缺點:

  1. 循環時間太長,我們看CAS的底層源碼可以發現,程序可能一直都在while循環裏面循環,知道等到主存的值和工作內存中的值一樣,纔可以跳出循環
  2. 只能保證一個共享變量的原子操作
  3. 引發ABA問題

 

什麼是ABA問題

 白話解釋:我們已經理解了CAS算法,比如有兩個線程A,B,線程B執行的速度非常快,線程B把初始值100改爲101,又把101改爲100,如果此時線程A進行更新時,就會發現,他的本地緩存值和主存的值一樣,他就會認爲這個值沒有被改變過,這個過程就是ABA問題,我們可以發現這過程中存在些貓膩

 ABA的解決方案:利用類似數據庫樂觀鎖的機制,把每次更新操作都對應一個版本號,線程A去更新的時候,不光要判斷當前線程的緩存值和主存的值是否一樣,還要判斷他拿到的版本號是否一致,AtomicStampedReference可以用來解決這個問題

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章