JAva原子類の原理とCAS

5678 ワード

CAS CASのフルネームはCompare-And-Swapと呼ばれ、直訳するとコントラスト交換になります.CPUの原子指令で、2つの値が等しいかどうかを比較してから、ある位置の値を原子的に更新する役割を果たす.調査の結果、その実現方式はハードウェアプラットフォームに基づくアセンブリ指令であり、CASはハードウェアによって実現され、JVMはアセンブリ呼び出しをカプセル化しただけで、それらのAtomicInteger類はこれらのカプセル化されたインタフェースを使用していることが分かった.簡単な説明:CAS操作には2つの数値を入力する必要があります.古い値(所望の操作前の値)と新しい値は、操作期間中に古い値に変化がないかを比較してから新しい値に交換し、変化がなければ交換しない.(synchronizedヘビー級ロック)原子の更新を維持するために使用されます.
CASを使用しない場合は、高パラレルでマルチスレッドが変数の値を同時に変更するにはsynchronizedロックが必要です(ロックでロックできると言われているかもしれませんが、ロックの下部のAQSもCASに基づいてロックを取得しています).
public class Test {
    private int i=0;
    public synchronized int add(){
        return i++;
    }
}

JAvaではAtomicInteger原子クラス(下位層がCASに基づいてデータを更新する)を提供し,ロックを必要とせずにマルチスレッド同時シーンでデータの整合性を実現した.
public class Test {
    private  AtomicInteger i = new AtomicInteger(0);
    public int add(){
        return i.addAndGet(1);
    }
}

java.util.concurrentパッケージの実装クラスはvolatileとCASに基づいて実装される.特にjava.util.concurrent.atomicパケット下の原子類.
volatileのプロパティを簡単に説明します.
メモリ可視性(あるスレッドがvolatile変数の値を変更すると、別のスレッドがこの変数の更新値をリアルタイムで見ることができる)禁止命令再配置(volatile変数の前の変数はvolatile変数より先に実行され、volatileの後の変数はvolatile変数の後に実行される)AtomicIntegerソースコード解析
public class AtomicInteger extends Number implements java.io.Serializable {
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;
    static {
        try {
            //    value         “    ”    
            valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

    private volatile int value;

    //     
    public final int get() {
        return value;
    }

    //   detla
    public final int getAndAdd(int delta) {
        //    ,1、      2、value         3、  value     (value+delta)。
        return unsafe.getAndAddInt(this, valueOffset, delta);
    }

    //   1
    public final int incrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }
}

AtomicIntegerの下位層ではvolatileの変数とCASを使用してデータを変更していることがわかります.volatileはスレッドの可視性を保証し、マルチスレッドが同時である場合、1つのスレッドがデータを修正し、他のスレッドが直ちに修正後の値CASを見てデータ更新の原子性を保証することができる.
Unsafeソースコード解析は、以下に、Unsafeクラスにおける実装を解析する.コードが逆コンパイルされた.
public final int getAndAddInt(Object paramObject, long paramLong, int paramInt)
  {
    int i;
    do
      i = getIntVolatile(paramObject, paramLong);
    while (!compareAndSwapInt(paramObject, paramLong, i, i + paramInt));
    return i;
  }

  public final long getAndAddLong(Object paramObject, long paramLong1, long paramLong2)
  {
    long l;
    do
      l = getLongVolatile(paramObject, paramLong1);
    while (!compareAndSwapLong(paramObject, paramLong1, l, l + paramLong2));
    return l;
  }

  public final int getAndSetInt(Object paramObject, long paramLong, int paramInt)
  {
    int i;
    do
      i = getIntVolatile(paramObject, paramLong);
    while (!compareAndSwapInt(paramObject, paramLong, i, paramInt));
    return i;
  }

  public final long getAndSetLong(Object paramObject, long paramLong1, long paramLong2)
  {
    long l;
    do
      l = getLongVolatile(paramObject, paramLong1);
    while (!compareAndSwapLong(paramObject, paramLong1, l, paramLong2));
    return l;
  }

  public final Object getAndSetObject(Object paramObject1, long paramLong, Object paramObject2)
  {
    Object localObject;
    do
      localObject = getObjectVolatile(paramObject1, paramLong);
    while (!compareAndSwapObject(paramObject1, paramLong, localObject, paramObject2));
    return localObject;
  }

ソースコードから,内部ではスピン方式でCAS更新が行われていることが分かった(whileループはCAS更新を行い,更新に失敗するとループは再試行する).
また,Unsafeクラスから,原子操作は実際には次の3つの方法しかサポートされていないことが分かった.
  public final native boolean compareAndSwapObject(Object paramObject1, long paramLong, Object paramObject2, Object paramObject3);

  public final native boolean compareAndSwapInt(Object paramObject, long paramLong, int paramInt1, int paramInt2);

  public final native boolean compareAndSwapLong(Object paramObject, long paramLong1, long paramLong2, long paramLong3);

UnsafeはcompareAndSwapObject,compareAndSwapInt,compareAndSwapLongの3つのCAS法しか提供していないことを見出した.いずれもnativeメソッドです.
AtomicBooleanソースコード解析
public class AtomicBoolean implements java.io.Serializable {
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;

    static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicBoolean.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

    private volatile int value;

    public AtomicBoolean(boolean initialValue) {
        value = initialValue ? 1 : 0;
    }
    public final boolean compareAndSet(boolean expect, boolean update) {
        int e = expect ? 1 : 0;
        int u = update ? 1 : 0;
        return unsafe.compareAndSwapInt(this, valueOffset, e, u);
    }
}

AtomicBooleanソースコードから、彼の下位層もvolatileタイプのint変数を使用していることがわかり、AtomicInteger実装方式と同様に、Booleanを0と1に変換して操作するにすぎない.
したがって、原子更新char、float、double変数もintまたはlongに変換してCASの動作を実現することができる.
CAS欠点ABA問題.CASは、操作値のときに値が変化しているかどうかをチェックする必要があるので、変化がなければ更新しますが、1つの値がAで、Bになって、Aになっていると、CASを使ってチェックすると値が変化していないことがわかりますが、実際には変化しています.ABA問題の解決策はバージョン番号を使うことです.変数の前にバージョン番号を追加し、変数が更新されるたびにバージョン番号を1つ追加すると、A-B-Aは1 A-2 B-3 Aになります.Java 1から5からJDKのatomicパッケージにABA問題を解決するためのクラスAtomicStampedReferenceが提供された.このクラスのcompareAndSetメソッドは、まず、現在の参照が所望の参照に等しいかどうかを確認し、現在のフラグが所望のフラグに等しいかどうかを確認し、すべてが等しい場合、その参照とフラグの値を所定の更新値に原子的に設定することです.サイクル時間が長くてオーバーヘッドが大きい.スピンCASが長時間失敗すると、CPUに非常に大きな実行コストがかかります.