Java CAS底層原理

Java CAS底層原理

Java CAS底層原理,這一篇就夠了!!!

CAS全稱(Conmpare And Swap)比較並交換,是一種用於在多線程環境下實現同步功能的機制。CAS 操作包含三個操作數 – 內存地址、預期值和新值。CAS 的實現邏輯是將內存地址的數值與預期數值想比較,若相等,則將內存位置處的值替換爲新值。若不相等,則不做任何操作。

JAVA中CAS是通過自旋操作完成賦值,若值不相等再更新預期值、重新計算新值,接着進行CAS操作,直到成功爲止。底層是JVM調用操作系統原語指令unsafe,並由CPU完成原子操作,你要知道併發/多線程環境下如果CPU沒有原子操作我們是無法完成。

  • JAVA1.5開始引入了CAS,主要代碼都放在JUC的atomic包下,如下圖:
    在這裏插入圖片描述
  • JUC包下源碼如下:
    在這裏插入圖片描述
  • 每一個操作都是調用unsafe方法實現結果。這是java自旋完成CAS源碼:
    在這裏插入圖片描述

CAS優點

  • 沒有引用鎖的概念,併發量不高情況下提高效率
  • 減少線程上下文切換

CAS缺點

  • cpu開銷大,在高併發下,許多線程,更新一變量,多次更新不成功,循環反覆,給cpu帶來大量壓力。
  • 只是一個變量的原子性操作,不能保證代碼塊的原子性。
  • ABA問題

ABA問題

CAS帶來最大問題就是ABA問題。有A、B兩個線程,A線程運行10s,B線程運行2s,兩個線程同一時間開始運行都修改同一變量m,假設m初始值爲5,B線程修改m值5改爲10,再修改m值10改爲5。此時A線程修改m值5改爲6,修改成功,滿足CAS操作原理,而A線程認爲m一直都是5,它並不知道m的值已經被修改過。

解決該問題通過添加版本號來解決,每次修改都帶着本次修改的版本號,發現版本不是當前版本此修改失敗,否則修改成功。如果自旋同理重新獲得版本號計算舊值、新值等。

public class ABA {

    //ABA問題產生與解決
    static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);
    static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1);

    public static void main(String[] args) {
        //==========ABA問題的產生==========
        System.out.println("==========ABA問題的產生==========");

        new Thread(() -> {
            atomicReference.compareAndSet(100, 101);
            atomicReference.compareAndSet(101, 100);
        }, "t1").start();

        new Thread(() -> {
            //保證t1線程完成
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(atomicReference.compareAndSet(100, 2019) + "\t" + atomicReference.get());
        }, "t2").start();

        //暫停一會
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //==========ABA問題的解決==========
        System.out.println("==========ABA問題的解決==========");

        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp();
            System.out.println(Thread.currentThread().getName() + "\t第一次版本號:" + stamp);

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            atomicStampedReference.compareAndSet(100, 101, stamp, stamp + 1);
            System.out.println(Thread.currentThread().getName() + "\t第二次版本號:" + atomicStampedReference.getStamp());
            atomicStampedReference.compareAndSet(101, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
            System.out.println(Thread.currentThread().getName() + "\t第三次版本號:" + atomicStampedReference.getStamp());
        }, "t3").start();

        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp();
            System.out.println(Thread.currentThread().getName() + "\t第一次版本號:" + stamp);

            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            boolean result = atomicStampedReference.compareAndSet(100, 101, stamp, stamp + 1);
            System.out.println(Thread.currentThread().getName() + "\t修改成功否" + result + "\t當前最前版本號:" + atomicStampedReference.getStamp());
            System.out.println(Thread.currentThread().getName() + "\t當前實際值:" + atomicStampedReference.getReference());
        }, "t4").start();
    }
}

  • 運行結果:
    在這裏插入圖片描述
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章