CAS個人理解

4997 ワード

CASって何?


CAS(Compare And Swap)はcup合併原語であり,その本質は比較し交換することである.
CASインスタンス
public class CASDemo {
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(5);
        atomicInteger.compareAndSet(5, 2019);
        System.out.println(atomicInteger.get());
        atomicInteger.compareAndSet(2019, 2000);
        System.out.println(atomicInteger.get());
        }
  }

CASの下地
スピンロック+Unsafeクラス
Unsafeクラスとは
Javaが直接アクセスできない下位システムメモリのため、c言語のようにポインタを直接介して変数のストレージアドレスにアクセスすることはできません.JAvaは、下位システムのデータストレージアドレスにローカルメソッドでアクセスする必要がありますが、Unsafeクラスは、バックドアに相当する限界を補います.Unsafeでは、特定のメモリを操作するデータに直接アクセスできるnativeメソッドが多数用意されています.c言語のポインタと似ています.
スピンロックとは
スピンロックは共有リソースを保護するために提案されたロックメカニズムである.
コードの原理は次のとおりです.
 public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

        return var5;
    }

上記のコードにおいてvar 1がオブジェクトが操作するオブジェクト参照var 2は、そのオブジェクト参照のメモリにおけるオフセット量、すなわち、ホームアドレスvar 5が所望値var 5+var 4が更新される値であるというコードの意味は、共有変数の記憶アドレスに基づいてその変数の値を取得し、その変数の値が所望値に等しいか否かを判断し、等しい場合は更新されるメインメモリにおけるその変数の値を最新の値とすることである.逆に、メインメモリの値と期待値が一致するまで比較を続けます.CASコンカレント原語はJava言語でUnsafeクラスの各方法であり、java条はUnsafeクラスの方法であり、JVMは自動的にCASのアセンブリ命令を実現してくれる.これはハードウェアに完全に依存する機能であり、それによって原子的な操作を体現し、CASはシステム原語であり、オペレーティングシステム命令に属し、いくつかの命令からなるコードブロックであり、実行は連続性でなければならず、途中で中断することはできない.
CASの欠点:1、サイクル時間が長く、メモリ消費が大きい;スレッド数が多いとCASリクエストの失敗がループしてしまい、CPUのオーバーヘッドが大きくなります.2、共有変数に対して原子的な操作しかできない3、ABAの問題が発生する.
ABAの問題は何ですか.CASは、変更回数や変更の有無など、メインメモリに変数を共有する操作を記録できないためです.これにより、一方のスレッドがAをBに変更し、他方のスレッドがBをAに変更してCASが複数回実行されるという問題が発生しやすい.すなわち,先頭と末尾は同じであり,中間過程が変化した.原子参照アトミックReferenceは基本的なタイプの原子性を保証するものであり、1つのクラスでは原子参照を使用してカプセル化することができる.
AtomicReference userAtomic =  new AtomicReference<>();

ABA問題ABA問題をどのように解決するかという本質は,CASによってメインメモリの共有変数の値を複数回変更し,最終的には最初の値に変更することである.先頭と末尾のように,中間の操作は他のスレッドでは不明であり,CASの複数回の実行をもたらす可能性がある.ABA問題を解決する方法は、他のスレッドにこの変数の間に変更があったことを知らせることであり、変数が変更されるたびにレコードを追加するだけでこの問題を解決することができます.AtomicStampedReferenceは、既存のAtomicReferenceにバージョン番号の概念を追加しています.使用例
public class ABADemo { //ABA ,AtomicStampedReference

    static AtomicReference atomicReference = new AtomicReference<>(100);
    static AtomicStampedReference atomicStampedReference = new AtomicStampedReference<>(100, 1);
    public static void main(String[] args) {
        new Thread(()->{
            atomicReference.compareAndSet(100, 101);
            atomicReference.compareAndSet(101, 100);
        }, "t1").start();
        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(1);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            System.out.println(atomicReference.compareAndSet(100, 100) + "\t" + atomicReference.get());
        },  "t2").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        }catch (InterruptedException e){
            e.printStackTrace();
        }

        System.out.println("******************* ABA ********************");
        new Thread(()->{
            int stamp = atomicStampedReference.getStamp(); // 
            System.out.println(Thread.currentThread().getName() + " :" + stamp);
            try { TimeUnit.SECONDS.sleep(1); }catch (InterruptedException e){ e.printStackTrace();}
            atomicStampedReference.compareAndSet(100, 101, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
            System.out.println(Thread.currentThread().getName() + " :" + atomicStampedReference.getStamp());
            atomicStampedReference.compareAndSet(101, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
            System.out.println(Thread.currentThread().getName() + " :" + atomicStampedReference.getStamp());
        }, "t3").start();

        new Thread(()->{
            int stamp = atomicStampedReference.getStamp();
            System.out.println(Thread.currentThread().getName() + " :" + atomicStampedReference.getStamp());
            try{ TimeUnit.SECONDS.sleep(3);}catch (InterruptedException e){e.printStackTrace();}
           boolean result =  atomicStampedReference.compareAndSet(100, 2019, stamp, stamp + 1);
            System.out.println(Thread.currentThread().getName() + " ?" + result + " " + atomicStampedReference.getStamp());
            System.out.println(Thread.currentThread().getName() + " " + "\t" + atomicStampedReference.getReference());
        }, "t4").start();
    }
}

タイムスタンプ原子参照を使用すると、スレッドが値を変更している間に、メモリの値が予想値と同じであるにもかかわらず、バージョン番号が変更されたため、変更に失敗した場合、変数のバージョン番号と変数の値を再取得して変更し、新しいバージョンとしてマークする必要があります.