Atomicクラスがどのように原子性を保証するか

11356 ワード

CAS
フルネームはCompareAndSwapで、CPUコンカレント原語です.メモリの位置の値が予想値であるかどうかを判断するために使用され、もしそうであれば更新された値に変更されます.このプロセスは原子です.AtomicIntegerが原子性を保証できるのはUnSafe類に依存している.この類はJavaの最下層の類の一つで、中にはキックアスのnative方法があり、他の言語で書かれているので、見えない.Unsafe類は以下のいくつかの操作を実行することができる.
  • メモリを割り当て、
  • メモリを解放する
  • は、オブジェクトの属性のメモリ内の位置を特定し、オブジェクトの属性値を変更することができる.objectFieldOffsetメソッド
  • を使用
  • スレッドは、
  • を使用するためにLockSupportクラスにカプセル化されている.
  • CAS

  • 関連ソース
     public final class Unsafe {
        //       
         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;
            }
    }
    
    public class AtomicInteger extends Number implements java.io.Serializable {
        private static final long serialVersionUID = 6214790243416807050L;
    
        // setup to use Unsafe.compareAndSwapInt for updates
        private static final Unsafe unsafe = Unsafe.getUnsafe();
        private static final long valueOffset;
    
        static {
            try {
                //          
                valueOffset = unsafe.objectFieldOffset
                    (AtomicInteger.class.getDeclaredField("value"));
            } catch (Exception ex) { throw new Error(ex); }
        }
    
        private volatile int value;
      /**
         * Atomically increments by one the current value.
         *
         * @return the previous value
         */
        public final int getAndIncrement() {
            //this         
            return unsafe.getAndAddInt(this, valueOffset, 1);
        }
    }
    

    CASの欠点
  • サイクル時間が長いとCPUオーバーヘッドが大きい
  • は1つの共有変数の原子操作しか保証することができず、複数の共有変数操作の場合、サイクルCASは操作の原子性を保証することができず、この場合はロックのみを使用して
  • を保証することができる.
  • ABA問題
  • ABA
    CASアルゴリズムは,メモリ内のある時点のデータを取り出し,その時点で比較して置き換えるものであり,その間に時間差がありデータが変化する.たとえば、2つのスレッドがあります.1つは速くて遅くて、同時にメインメモリから変数Aを取り、速いスレッドは変数をBに変えてメインメモリに書き、それからBをメインメモリから取り出してからAに変えてメインメモリに書きます.このとき、遅いスレッドは仕事を終えたばかりで、CASアルゴリズムを使って、予想値がAなのかを発見し、遅いスレッドは自分の結果をメインメモリに書きます.スロースレッド操作は成功しましたが、このプロセスに問題がある可能性があります.
    原子参照
    AtomicReferenceは特定のオブジェクトを原子クラスにプログラミングするために使用され、AtomicReferenceとAtomicIntegerは非常に類似している.違いはAtomicIntegerが整数のパッケージであり、下位層はcompareAndSwapInt実装CASを採用し、数値が等しいかどうかを比較するが、AtomicReferenceは通常のオブジェクト参照に対応し、下位層はcompareAndSwapObject実装CASを使用し、2つのオブジェクトのアドレスが等しいかどうかを比較します.つまり、オブジェクト参照を変更するときのスレッドのセキュリティを保証します.
    原子参照タイムスタンプ
    ABAの問題では、値を比較しながらバージョン番号、またはタイムスタンプを付けることができます.AtomicStampedReferenceというクラスは、作成時に初期のバージョン番号を指定する必要があり、CAS操作を行うたびにバージョン番号を比較する必要があり、バージョン番号は自己増加する必要があります.
       /**
         * Creates a new {@code AtomicStampedReference} with the given
         * initial values.
         *
         * @param initialRef the initial reference
         * @param initialStamp the initial stamp
         */
        public AtomicStampedReference(V initialRef, int initialStamp) {
            pair = Pair.of(initialRef, initialStamp);
        }
    /**
         * Atomically sets the value of both the reference and stamp
         * to the given update values if the
         * current reference is {@code ==} to the expected reference
         * and the current stamp is equal to the expected stamp.
         *
         * @param expectedReference the expected value of the reference
         * @param newReference the new value for the reference
         * @param expectedStamp the expected value of the stamp
         * @param newStamp the new value for the stamp
         * @return {@code true} if successful
         */
        public boolean compareAndSet(V   expectedReference,
                                     V   newReference,
                                     int expectedStamp,
                                     int newStamp) {
            Pair<V> current = pair;
            return
                expectedReference == current.reference &&
                expectedStamp == current.stamp &&
                ((newReference == current.reference &&
                  newStamp == current.stamp) ||
                 casPair(current, Pair.of(newReference, newStamp)));
        }