天天看點

CAS導緻的ABA問題以及解決方案

CAS算法實作一個重要前提需要取出記憶體中某時刻的資料,而在下時刻比較并替換,那麼在這個時間差類會導緻資料的變化。

上篇文章講到CAS會出現一個ABA問題。那什麼是ABA問題呢?

官方一點的解釋就是:當有多個線程對一個原子類進行操作的時候,某個線程在短時間内将原子類的值A修改為B,又馬上将其修改為A,此時其他線程不感覺,還是會修改成功。

CAS導緻的ABA問題以及解決方案

代碼案例:

//線程操作資源,原子類ai的初始值為4
      static AtomicInteger ai = new AtomicInteger(4);
      public static void main(String[] args) {
        new Thread(() -> {
        //利用CAS将ai的值改成5
            boolean b = ai.compareAndSet(4, 5);
            System.out.println(Thread.currentThread().getName()+"是否成功将ai的值修改為5:"+b);
            //休眠一秒
            try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}
            //利用CAS将ai的值改回4
            b = ai.compareAndSet(5,4);
            System.out.println(Thread.currentThread().getName()+"是否成功将ai的值修改為4:"+b);
        },"A").start();
        new Thread(() -> {
            //模拟此線程執行較慢的情況
            try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}
            //利用CAS将ai的值從4改為10
            boolean b = ai.compareAndSet(4, 10);
            System.out.println(Thread.currentThread().getName()+"是否成功将ai的值修改為10:"+b);
        },"B").start();

        //等待其他線程完成,為什麼是2,因為一個是main線程,一個是背景的GC線程
        while (Thread.activeCount() > 2) {
            Thread.yield();
        }
        
        System.out.println("ai最終的值為:"+ai.get());
    }           

複制

執行結果:

CAS導緻的ABA問題以及解決方案

可以看到,線程B最終是将ai的值修改成功了。

上面例子模拟的是A、B兩個線程操作一個資源ai,A的執行速度比B的快,在B執行前,A就已經将ai的值改為5之後馬上又把ai的值改回為4,但是B不感覺,是以最後B就修改成功了。

比如有兩個單身狗A、B,A在某個時間段内找到女朋友但是又分開了,但是沒告訴B,此時B還是會在A是單身狗的情況下帶A去打遊戲。

ABA問題的解決方案?

資料庫有個鎖稱為樂觀鎖,是一種基于資料版本實作資料同步的機制,每次修改一次資料,版本就會進行累加。

同樣,Java也提供了相應的原子引用類AtomicStampedReference<V>

CAS導緻的ABA問題以及解決方案

上圖中的初始郵票就是版本号。

根據之前的代碼改動的例子:

static AtomicStampedReference<Integer> ai = new AtomicStampedReference<>(4,0);
    public static void main(String[] args) {
        new Thread(() -> {
            //四個參數分别是預估記憶體值,更新值,預估版本号,初始版本号
       //隻有當預估記憶體值==實際記憶體值相等并且預估版本号==實際版本号,才會進行修改
            boolean b = ai.compareAndSet(4, 5,0,1);
            System.out.println(Thread.currentThread().getName()+"是否成功将ai的值修改為5:"+b);
            try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}
            b = ai.compareAndSet(5,4,1,2);
            System.out.println(Thread.currentThread().getName()+"是否成功将ai的值修改為4:"+b);
        },"A").start();
        new Thread(() -> {
            try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}
            boolean b = ai.compareAndSet(4, 10,0,1);
            System.out.println(Thread.currentThread().getName()+"是否成功将ai的值修改為10:"+b);
        },"B").start();

        while (Thread.activeCount() > 2) {
            Thread.yield();
        }

        System.out.println("ai最終的值為:"+asri.getReference());
    }           

複制

運作結果:

CAS導緻的ABA問題以及解決方案

可以看到,最終B并沒有成功修改ai的值