CAS 和 ABA 問題

CAS簡介

CAS 全稱是 compare and swap,是一種用於在多線程環境下實現同步功能的機制。java

CAS 它是一條CPU併發原語。操做包含三個操做數 -- 內存位置、預期數值和新值。CAS 的實現邏輯是將內存位置處的數值與預期數值想比較,若相等,則將內存位置處的值替換爲新值。若不相等,則不作任何操做。這個過程是原子的。多線程

CAS併發原語體如今java語言中的sun.misc.Unsafe類中的各個方法。調用Unsafe類中的CAS方法,JVM會幫咱們實現彙編指令。這是一種徹底依賴硬件的功能,經過它實現了原子操做。因爲CAS是一種系統原語,原語屬於操做系統用語範疇,是由若干條指令組成的,用於完成某個功能的一個過程,而且原語的執行必須是連續的,在執行過程當中不容許被打斷,也就是說CAS是一條CPU的原子指令,不會形成所謂的數據不一致問題。併發

Unsafe類

Unsafe類是CAS的核心類,因爲Java方法沒法直接訪問底層系統,須要經過本地(native)方法來訪問,基於該類能夠直接操做特定內存的數據。Unsafe類存在與sum.misc包中,其內部方法操做能夠像C的指針同樣直接操做內存,由於Java中CAS操做的執行依賴於Unsafe類的方法。源碼分析

Unsafe類中的全部方法都是native修飾的,也就是說Unsafe類中的方法都直接調用操做系統底層資源執行相應任務。優化

代碼解析

public class CASDemo {
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(5);
        
        // 運行結果: true   2019
        System.out.println(atomicInteger.compareAndSet(5, 2019) + "\t" + atomicInteger.get());
        // 運行結果: false  2019
        System.out.println(atomicInteger.compareAndSet(5, 1024) + "\t" + atomicInteger.get());

        // 此方法能夠解決多線程環境下i++問題,底層使用的是Unsafe類CAS和自旋鎖
        atomicInteger.getAndIncrement();
    }
}

源碼分析:this

/**
     * Atomically increments by one the current value.
     *
     * @return the previous value
     */
    public final int getAndIncrement() {
        // this=當前對象 valueOffset=內存偏移量(內存地址) 1=固定值,每次調用+1
        // Unsafe就是根據內存偏移地址獲取數據的。
        return unsafe.getAndAddInt(this, valueOffset, 1);
    }
    
    /**
     * 爲了方便查看和添加註釋,此方法是從Unsafe類中複製出來的
     */
    public final int getAndAddInt(Object var1, long var2, int var4) {
            int var5;
            do {
                // 獲取var1對象,內存地址在var2的值。
                // 至關於這個線程從主物理內存中取值copy到本身的工做內存中。
                var5 = this.getIntVolatile(var1, var2);
                
                // 比較並交換,若是var1對象,內存地址在var2的值和var5值同樣,那麼就+1
                // compareAndSwapInt若是返回true,取反爲false,說明更新成功,退出循環,則返回。
                // compareAndSwapInt若是返回false,取反爲true,說明當前線程工做內存中的值和主物理內存中的值不同,被其餘線程修改了,則繼續循環獲取比較,直到更新成功爲止。
            } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
    
            return var5;
        }

執行過程說明:atom

  • 假設線程A和線程B兩個線程同時執行 getAndAddInt操做(分別跑在不一樣CPU上):
  • AtomicInteger裏面的value原始值爲3,即主內存中 AtomicInteger的value爲3,根據JMM模型,線程A和線程B各自持有一
    份值爲3的value的副本分別到各自的工做內存。
  • 線程A經過 getIntVolatile(var1,var2)拿到value值3,這時線程A被掛起。
  • 線程B也經過 getIntVolatile(var1,var2)方法獲取到value值3,此時恰好線程B沒有被掛起並執行 compareAndSwapInt方法
    比較內存值也爲3,成功修改內存值爲4,線程B改完收工,一切OK。
  • 這時線程A恢復,執行 compareAndSwapInt方法比較,發現本身手裏的值數字3和主內存的值數字4不一致,說明該值已
    經被其它線程搶先一步修改過了,那A線程本次修改失敗,只能從新讀取從新來一遍了。
  • 線程A從新獲取 value值,由於變量value被 volatile修飾,因此其它線程對它的修改,線程A老是可以看到,線程A繼續執
    了 compareAndSwapInt進行比較替換,直到成功。

volatile簡單說明:
volatile是一個輕量級的同步機制, 三大特性: 保證可見性, 不保證原子性, 禁止指令重排。操作系統

  • 可見性: 多個線程從主內存中copy一份數據,修改後,須要將本身的數據從新寫入主內存,並通知其餘線程數據已更新,保證數據可見性,和多線程數據一致性。
  • 禁止指令重排: 因爲指令重排,會對代碼的執行順序進行優化,可能會致使最後的結果和指望的結果不一致,因此須要禁止重排。

CAS的優缺點

優勢線程

  • 不須要加鎖,保持了一致性和併發性。

缺點指針

  • 循環時間長開銷很大:咱們能夠看到getAndAddInt方法執行時,若是CAS失敗,會一直進行嘗試。若是CAS長時間一直不成功,可能會給CPU帶來很大的開銷。
  • 只能保證一個共享變量的原子操做:當對一個共享變量執行操做時,咱們可使用循環CAS的方式來保證原子操做,可是對多個共享變量操做時,循環CAS就沒法保證操做的原子性,這個時候就能夠用鎖來保證原子性。
  • ABA問題:下面會提供詳細案例

ABA問題

舉個栗子說明:

主內存有個數據值:A,兩個線程A和B分別copy主內存數據到本身的工做區,A執行比較慢,須要10秒, B執行比較快,須要2秒, 此時B線程將主內存中的數據更改成B,過了一會又更改成A,而後A線程執行比較,發現結果是A,覺得別人沒有動過,而後執行更改操做。其實中間已經被更改過了,這就是ABA問題。

也就是ABA問題只要開始時的數據和結束時的數據一致,我就認爲沒改過,無論過程。

儘管A線程的CAS操做是成功的,可是不表明這個過程就是沒問題的。

ABA問題說簡單點就是,預判值仍是和當初抓取的同樣,可是這個「 值 」的版本可能不同了,在某些不只要考慮數據值是否一致,還要考慮版本是否一致的場景下須要注意.

Java併發包爲了解決這個問題,提供了一個帶有標記的原子引用類「AtomicStampedReference」,它能夠經過控制變量值的版原本保證CAS的正確性。

解決ABA問題的代碼示例

/**
 * 解決CAS的ABA問題
 */
public class SolveABAOfCAS {

    static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);
    static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1);

    public static void main(String[] args) throws InterruptedException {
        System.out.println("==========如下是ABA問題的產生==========");
        new Thread(() -> {
            atomicReference.compareAndSet(100, 101);
            atomicReference.compareAndSet(101, 100);
        }, "t1").start();

        new Thread(() -> {
            try {
                // 暫停1秒鐘,保證上面完成一次ABA操做
                Thread.sleep(1000);
                System.out.println(atomicReference.compareAndSet(100, 2019) + "\t" + atomicReference.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "t2").start();

        Thread.sleep(2000);
        System.out.println("==========如下是ABA問題的解決==========");
        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp();
            System.out.println(Thread.currentThread().getName() + "\t第1次版本號" + stamp);
            try {
                // 暫停一秒鐘t3線程
                Thread.sleep(1000);
                atomicStampedReference.compareAndSet(100, 101, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
                System.out.println(Thread.currentThread().getName() + "\t第2次版本號" + atomicStampedReference.getStamp());
                atomicStampedReference.compareAndSet(101, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
                System.out.println(Thread.currentThread().getName() + "\t第3次版本號" + atomicStampedReference.getStamp());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "t3").start();

        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp();
            System.out.println(Thread.currentThread().getName() + "\t第1次版本號" + stamp);
            try {
                // 暫停3秒鐘t4線程,保證上面的t3線程完成一次ABA操做
                Thread.sleep(3000);
                boolean result = atomicStampedReference.compareAndSet(100, 2019, stamp, stamp + 1);
                System.out.println(Thread.currentThread().getName() + "\t修改爲功否: " + result + "\t當前最新實際版本號: " + atomicStampedReference.getStamp());
                System.out.println(Thread.currentThread().getName() + "\t當前實際最新值: " + atomicStampedReference.getReference());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "t4").start();

    }
}

若是以爲對你有幫助,歡迎來訪個人博客:http://jianjieming.com

相關文章
相關標籤/搜索