ABA問題の理解


ABA問題の理解
  • 1 ABA問題の発生
  • 2原子参照AtomicReference
  • 3タイムスタンプ付き原子参照AtomicStampedReference ABA問題解決
  • 1 ABA問題の発生
    ABA問題とは,比較して交換するサイクルであり,時間差が存在するが,この時間差は予想外の問題をもたらす可能性がある.例えば、スレッド1とスレッド2は同時にメモリからAを取り出し、スレッドT 1は値をAからBに変更し、BからAに変更する.スレッドT 2が見た最終値はAであり,事前推定値との比較を経て両者は等しく更新可能であるが,このときスレッドT 2のCAS動作は成功したが,問題がないわけではない.
    CASのように、頭と尾の一致だけを重視し、頭と尾が一致すれば受け入れる需要があります.しかし,プロセスを重視する需要もあり,途中で何の修正も起こらず,AtomicReference原子参照を引き出した.
    2原子参照AtomicReference
    AtomicIntegerは整数に対して原子操作を行い、AtomicIntegerは長整数型数に対して原子操作を行い、AtomicBooleanはブール型数に対して原子操作を行うが、実際にはこれらは全く足りない.POJOなら?このPOJOをAtomicReferenceで包装し,操作を原子化することができる.
    Class AtomicReference,Valueは原子包装が必要な汎用クラスである.
    例:
    @Getter
    @ToString
    @AllArgsConstructor
    class User {
         
        String userName;
        int age;
    }
    
    public class AtomicRefrenceDemo {
         
        public static void main(String[] args) {
         
            User z3 = new User("  ", 22);
            User l4 = new User("  ", 23);
            AtomicReference<User> atomicReference = new AtomicReference<>();
            atomicReference.set(z3);
            System.out.println(atomicReference.compareAndSet(z3, l4) + "\t" + atomicReference.get().toString());
            System.out.println(atomicReference.compareAndSet(z3, l4) + "\t" + atomicReference.get().toString());
        }
    }
    

    出力結果:
    true User(userName=李四、age=23)false User(userName=李四、age=23)
    では、私たちはどのように原子参照に基づいてABA問題を解決するか、タイムスタンプ付き原子参照AtomicStampedReferenceを見てください.
    3タイムスタンプ付き原子参照AtomicStampedReference ABA問題解決
    AtomicStampedReferenceクラスを使用するとABAの問題を解決できます.このクラスは「バージョン番号」Stamp」を維持し、CAS操作を行う場合、現在の値だけでなくバージョン番号も比較します.両方が等しい場合にのみ更新操作を実行します.
    コアメソッド:
    static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(initialRef, initialStamp);
    int stamp = atomicStampedReference.getStamp()
    
    AtomicStampedReference.compareAndSet(expectedReference,newReference,oldStamp,newStamp);
    

    例:
    public class ABADemo {
         
        static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);
        static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1);
    
        public static void main(String[] args) {
         
            System.out.println("=====   ABA     =====");
            new Thread(() -> {
         
                atomicReference.compareAndSet(100, 101);
                atomicReference.compareAndSet(101, 100);
            }, "Thread 1").start();
    
            new Thread(() -> {
         
                try {
         
                    //    1    ABA  
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
         
                    e.printStackTrace();
                }
                System.out.println(atomicReference.compareAndSet(100, 2019) + "\t" + atomicReference.get());
            }, "Thread 2").start();
            try {
         
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
         
                e.printStackTrace();
            }
            System.out.println("=====   ABA     =====");
    
            new Thread(() -> {
         
                int stamp = atomicStampedReference.getStamp();
                System.out.println(Thread.currentThread().getName() + "\t 1    " + stamp);
                try {
         
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
         
                    e.printStackTrace();
                }
                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());
            }, "Thread 3").start();
    
            new Thread(() -> {
         
                int stamp = atomicStampedReference.getStamp();
                System.out.println(Thread.currentThread().getName() + "\t 1    " + stamp);
                try {
         
                    TimeUnit.SECONDS.sleep(4);
                } catch (InterruptedException e) {
         
                    e.printStackTrace();
                }
                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());
            }, "Thread 4").start();
        }
    }
    

    出力結果:
    =====以下の場合ABA問題の発生=====true 2019====以下の場合ABA問題の解決=====Thread 3第1次バージョン番号1//初期バージョン番号Thread 4第1次バージョン番号1//初期バージョン番号Thread 3第2次バージョン番号2//第1次修正後のバージョン番号Thread 3第3次バージョン番号3//第2次修正後のバージョン番号Thread 4修正がfalse現在の最新実のバージョン番号:3//修正に失敗し、この時T 4のバージョン番号は1+1であるが、実際のT 3はすでにバージョン番号を3に増加し、T 4修正に失敗したThread 4の現在の最新の実際値:100
    			try {
         
        			TimeUnit.SECONDS.sleep(2);
    			} catch (InterruptedException e) {
         
        			e.printStackTrace();
    			}
    

    T 3スレッドは1回目のバージョン番号を取得してから2秒間睡眠し、T 4スレッドが同じ初期バージョン番号を取得できることを保証します.
                try {
         
                    TimeUnit.SECONDS.sleep(4);
                } catch (InterruptedException e) {
         
                    e.printStackTrace();
                }
    

    T 4スレッドは1回目のバージョン番号を取得してから4秒睡眠し、その間にT 3スレッドがABA操作を完了したことを保証する.
    atomicStampedReference.compareAndSet(100, 101, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
    

    1番目のパラメータは推定値、2番目のパラメータは更新値、3番目のパラメータは推定バージョン番号、4番目のパラメータは更新バージョン番号を表します.推定値がメモリ実績値と等しく、推定バージョン番号が実績バージョン番号と等しい場合、更新メモリ値は更新値であり、更新バージョン番号は更新バージョン番号である.