Java深入學習30:CAS中的ABA問題以及解決方案


Java深入學習30:CAS中的ABA問題以及解決方案

 什么是ABA問題

  在CAS算法中,需要取出內存中某時刻的數據(由用戶完成),在下一時刻比較並替換(由CPU完成,該操作是原子的)。這個時間差中,會導致數據的變化。

  假設如下事件序列:

  1. 線程 1 從內存位置V中取出A。
  2. 線程 2 從位置V中取出A。
  3. 線程 2 進行了一些操作,將B寫入位置V。
  4. 線程 2 將A再次寫入位置V。
  5. 線程 1 進行CAS操作,發現位置V中仍然是A,操作成功。

盡管線程 1 的CAS操作成功,但不代表這個過程沒有問題——對於線程 1 ,線程 2 的修改已經丟失;我們不能忽略線程2對數據的兩次修改

 

代碼模擬ABA問題

  線程1,對數據100進行了兩次操作,先將100改成101,再將101改回100;線程2直接將100該成2020;雖然線程2修改成功了,但是在線程2修改之前,線程1已經對100進行了兩次操作。線程2修改的100並不是原來的那個100了;

public class ABATest {

    public static void main(String[] args) {

        AtomicInteger at = new AtomicInteger(100);

        new Thread(()->{
            System.out.println(Thread.currentThread().getName() + "\t" + at.compareAndSet(100,101) + "\t num = " + at.get());
            System.out.println(Thread.currentThread().getName() + "\t" + at.compareAndSet(101,100) + "\t num = " + at.get());
        },"thread1").start();
        
        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "\t" + at.compareAndSet(100,2020) + "\t num = " + at.get());
        },"thread2").start();
    }
}
日志
thread1    true     num = 101
thread1    true     num = 100
thread2    true     num = 2020

 

 

如何規避ABA問題

  使用AtomicStampedReference類,簡單說AtomicStampedReference類引入了版本概念(類似數據庫使用版本號進行樂觀鎖),每次進行compareAndSet操作是都進行版本好的迭代,只有當同時滿足CAS的(1)期望值正確匹配(2)版本號正確匹配,才能正確compareAndSet。

  如下示例,線程2中的 compareAndSet  因為版本匹配錯誤而返回 flase;

public class ABASolvedTest {

    public static void main(String[] args) {
        AtomicStampedReference<Integer> asr = new AtomicStampedReference(100,1);

        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "\t第1次版本號" + asr.getStamp() + "\t 當前值" + asr.getReference());
            asr.compareAndSet(100,101,asr.getStamp(),asr.getStamp()+1);
            System.out.println(Thread.currentThread().getName() + "\t第2次版本號" + asr.getStamp() + "\t 當前值" + asr.getReference());
            asr.compareAndSet(101,100,asr.getStamp(),asr.getStamp()+1);
            System.out.println(Thread.currentThread().getName() + "\t第3次版本號" + asr.getStamp() + "\t 當前值" + asr.getReference());
        },"thread1").start();
        new Thread(()->{
            int stamp = asr.getStamp();
            System.out.println(Thread.currentThread().getName() + "\t第1次版本號" +stamp);
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            boolean b = asr.compareAndSet(100, 2019, stamp, stamp + 1);
            System.out.println(Thread.currentThread().getName() + "\t是否更新成功 " + b);
            System.out.println(Thread.currentThread().getName() + "\t更新后的版本號" + asr.getStamp());
            System.out.println(Thread.currentThread().getName() + "\t更新后的值" + asr.getReference());
        },"thread2").start();
    }
}
日志
thread2    第1次版本號1
thread1    第1次版本號1     當前值100
thread1    第2次版本號2     當前值101
thread1    第3次版本號3     當前值100
thread2    是否更新成功 false
thread2    更新后的版本號3
thread2    更新后的值100

 

 

END


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM