CAS的ABA問題及解決方案

ABA問題

比如有兩個線程,線程T1和線程T2。線程T1的執行時間短(假如需要2s),線程T2的執行時間長(假如需要4s),也即是線程T2執行一次的時間,線程T1可以執行多次。

當線程T1將變量從A變成B,又從B變成A,這對線程T2而言,它是不知道該變量是否發生過變化的。可能會導致不可預知的問題。

通過原子類舉例

public static void main(String[] args) {

        AtomicInteger num=new AtomicInteger(0);
        //如果num的當前值是0 則把它更新爲1
        System.out.println(num.compareAndSet(0, 1));
        //如果num的當前值是1 則把它更新爲0
        System.out.println(num.compareAndSet(1, 0));
        //打印num當前值
        System.out.println(num.get());


    }

num的數據從0–>1,又從1–>0。不能說num沒有變化過。

ABA問題的解決

通過借鑑樂觀鎖的思路,爲數據添加上版本號來處理這個問題。JUC包中也提供了對應的解決方案AtomicStampedReference。這個類維護了一個“版本號”Stamp,在進行CAS操作的時候,不僅要比較當前值,還要比較版本號。只有兩者都相等,才執行更新操作。

public class StampDemo {

    private static AtomicStampedReference<Integer> stampedReference = new AtomicStampedReference<>(100, 1);

    public static void main(String[] args) {

        new Thread(() -> {

            try {
                int stamp = stampedReference.getStamp();

                System.out.println(Thread.currentThread().getName() + "\t第一次版本號: " + stamp);
                Thread.sleep(2 * 1000);
                stampedReference.compareAndSet(100, 101, stamp, stamp + 1);

                System.out.println(Thread.currentThread().getName() + "\t第二次版本號: " + stampedReference.getStamp());
                stampedReference.compareAndSet(101, 100, stampedReference.getStamp(), stampedReference.getStamp() + 1);

                System.out.println(Thread.currentThread().getName() + "\t第一次版本號: " + stampedReference.getStamp());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }


        }, "T1").start();

        new Thread(() -> {
            try {
                Thread.sleep(4 * 1000);
                boolean result = stampedReference.compareAndSet(100, 2019, stampedReference.getStamp(), stampedReference.getStamp() + 1);
                System.out.println(Thread.currentThread().getName() + "\t修改成功與否:" + result + "  當前最新版本號" + stampedReference.getStamp());
                System.out.println(Thread.currentThread().getName() + "\t當前實際值:" + stampedReference.getReference());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "T2").start();


    }
}

輸出結果:

T1 第一次版本號: 1
T1 第二次版本號: 2
T1 第一次版本號: 3
T2 修改成功與否:true 當前最新版本號4
T2 當前實際值:2019

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