CASのJUC-深さ分析

アウトライン

CAS、すなわち、比較とスワップ、比較とスワップ。同期のコンポーネントであるDoug Leaのすばらしい神、CAS技術の広範な使用は、神々は、Javaのマルチスレッドの同時操作を実現しました。

AQS同期等アセンブリ全体、動作は、CASが達成原子、原子群、JDK 1.8の偶数ConcurrentHashMapのバージョンをベースとする、CAS +同期に合わせます。これは、CASが全体JUCの礎石である、と言うことができます。

CAS解析

3つのパラメータがCASである:メモリ値V、古いAの期待値は、Vの値が期待値Aのメモリの古い値に等しい場合にのみ、専用メモリ値が値V変更され、Bの値を更新しますBは、他に何もしません。次の擬似コード:

if (this.value == A) {
	this.value = B
	return true;
} else {
	return false;
}
复制代码

カテゴリJUC下の原子は、CASによって達成されます。ここではCASの実現を説明するために例をのAtomicIntegerします。次のように:

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;
复制代码

危険CASはコアクラスであり、Javaは直接基礎となるオペレーティング・システムにアクセスすることができないが、ローカルネイティブメソッドを介してアクセスされます。

安全でない、ハードウェアレベルアトミック操作を提供します。それにもかかわらず、JVMまたはバックドアを開きます。

メモリ内valueOffset変数のオフセット値は、安全ではない元の値がアドレスデータをシフトすることによって得られます。値、電流値、マルチスレッド環境を見て確実にするために修飾された揮発性の使用は、同じです。

AtomicInteger

私たちは、ソースコードを見て、説明を行うために#addAndGetのAtomicIntegerの()メソッドを持っています:

// AtomicInteger.java
public final int addAndGet(int delta) {
    return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
}

// Unsafe.java
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;
}
复制代码

内部コール危険#getAndAddInt(オブジェクトVAR1、長いVAR2、int型VAR4)法、#getAndAddIntで(オブジェクトVAR1、長いVAR2、int型VAR4)法、主として#compareAndSwapInt(オブジェクトVAR1、長いVAR2、INT VAR4、INTを表示しますVAR5)メソッド、次のように:

public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
复制代码

アドレスオブジェクト、オブジェクト、期待値、修正値:ローカルメソッドのための方法、を表す4つのパラメータは、あります

CPUアトミック操作

CASは、読み取りを保証することができます - 変更 - 書き込み操作は、単一のプロセッサ上で実装が容易である原子操作ですが、実装はやや複雑マルチプロセッサです。

ロックまたはキャッシュするためのバスロック:CPUは、マルチプロセッサアトミック操作を達成するための2つの方法を提供します。

  1. バスロック:バスがロックされ、プロセッサによって提供LOCK#信号を使用することで、他のプロセッサを要求し、バス上の信号は、ライブブロックされ、このプロセッサの出力は、次に、プロセッサは、共有メモリの排他的使用を有します。しかし、このアプローチは、彼は、他のプロセッサが少し大きいの費用がかかり、他のメモリアドレスからのデータを使用することはできませんロック中に、ロックされたCPUとメモリとの間の通信を入れ、親切ではない、少し横柄です。だから、キャッシュロックがあります。

  2. キャッシュロック:実際には、のために上記のような場合には、我々は唯一のアトミックであるメモリアドレスに作用し、同時に確認する必要があります。キャッシュロックは、ロック中のデータは、それが戻ってメモリに書き込み行うロック操作は、プロセッサがもはや出力LOCK#信号がある場合にメモリ領域にキャッシュされたが、内部メモリアドレス、キャッシュコヒーレンシプロトコルの使用を変更しています保証アトミック。同じデータのメモリ領域を確保するためにキャッシュコヒーレンシ機構は、それがCPU1は私が次にCPU2は、キャッシュラインながら、キャッシュされません、キャッシュロックを使用して、キャッシュ・ラインを変更したとき、であり、唯一つのプロセッサを変更することができます。

CAS欠陥

アトミック操作にCAS効率的なソリューションが、しかし、主に3つの態様の一部の欠陥があります。

  1. サイクル時間が長すぎます
  2. 共有変数はアトミック操作を保証することができます
  3. ABAの問題

サイクル時間が長すぎます

CASは、失敗した場合は?失敗したスピンCASは、長い時間のために、その後、彼は非常に大きなオーバーヘッドCPUをもたらす場合には、このような状況では、絶対に可能です。SynchronousQueueのBlockingQueueの:JUCでは、いくつかの場所では、次のようなCASスピンの数を制限します。

共有変数はアトミック操作を保証することができます

あなたは手段を持っている場合は、複数の共有変数は、変数のみの可変数に全体を置くために、当然のことながら、ロックを使用することができる場合CASは、唯一の共有変数のためにこれを知っているだろう実装した読み出し、CASを使用することは悪いことではありません。例えば、読み書きロックのハイとロー状態。

ABAの問題

CASは何の変化が更新されていない場合、動作値が変更されていないか確認する必要があります。しかし、このような状況があります:値は、Bとなり、その後、Aになっている場合は、チェックCASは時間が変更されていないでしょうが、本質的にはそれが変更されている、これが呼び出されますABAの問題。ABAの問題がどの溶液を加えたバージョン番号のため、すなわち、バージョン番号が各変更時に各変数に結合され、プラス1であり、すなわち、A - > B - > Aは、図1Aになる - > 2B - > 3A。

影響を及ぼす

生じたABAの問題の影響を説明するために例を使用します。

以下のリストがあり、次のとおりです。

我々はBと交換したい場合は、それが#compareAndSet(この、A、B)です。スレッド1は、スタックのうちB、A、スレッド2の前に次の操作を実行し、交換作業Bまで実行し、C、スタック、最終的なリストは以下の通り:

完了すると、B.next = NULLであるため、#compareAndSet(この、A、B)は、につながる、後スレッドが依然として1 A、次いで#compareAndSet(この、A、B)の成功を発見されたが、今回は問題があるだろうCは、唯一の変更スタック要素B、C紛失する理由を失っています。

ソリューションAtomicStampedReference

ABA CAS隠された問題は、解決策は、Javaを解決するためにAtomicStampedReferenceを提供し、バージョン番号です。AtomicStampedReference [E、整数]をパッケージングし、それによって問題のABAを回避する、オブジェクトバージョンスタンプスタンプをマークするタプル。このような場合のために、スレッド1が故障しました。

AtomicStampedReference #compareAndSet(...)メソッド、次のように:

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)));
}
复制代码

更新期待の参照、更新の参照、署名することが期待され、ロゴ:4つの方法それぞれのパラメータがあります。ソース部分はよく理解されています。

  1. ==引用予想電流基準
  2. ==現在の身元を特定することが期待
  3. 基準マークと更新され、現在の参照と等号、直接リターンtrueの場合
  4. 対#(T基準、INTスタンプ)法により、現在のペアCASを置き換え、新しいペア・オブジェクトを生成します。

一対AtomicStampedReference主に記録し、次のように定義されたタイムスタンプ情報(識別)の基準バージョンに使用される内部クラス:

// AtomicStampedReference.java

private static class Pair<T> {
    final T reference; // 对象引用
    final int stamp; // 版本戳
    private Pair(T reference, int stamp) {
        this.reference = reference;
        this.stamp = stamp;
    }
    static <T> Pair<T> of(T reference, int stamp) {
        return new Pair<T>(reference, stamp);
    }
}

private volatile Pair<V> pair;
复制代码
  1. ペアは、intにスタンプ、スタンプバージョンのオブジェクト参照とバージョンを記録増分を続けます。一方のペアは、そのすべての属性がすべての最終のように定義、不変オブジェクトです。新しいペアオブジェクトを返す#OF(T基準、INTスタンプ)方法、外部。

  2. マルチスレッド環境の視認性を確保するために、揮発性として規定対属性。AtomicStampedReferenceでは、ほとんどの方法は、新しいペア・オブジェクトを生成する#OFのペア(T基準、INTスタンプ)メソッドを呼び出すことであり、その後、変数のペアに割り当てます。集合(V newReference、INT newStamp)法、次のように

// AtomicStampedReference.java
public void set(V newReference, int newStamp) {
    Pair<V> current = pair;
    if (newReference != current.reference || newStamp != current.stamp)
        this.pair = Pair.of(newReference, newStamp);
}
复制代码

実際のケース

以下に、私たちは例を使用します、我々は違いAtomicStampedReferenceとのAtomicIntegerを見ることができます。> 110 - - 私たちは一つのスレッドは、100の原因である、二つのスレッドを定義する2つの違いを参照、> 120 - > 100、スレッド2は100を実行します。

public class Test {
    private static AtomicInteger atomicInteger = new AtomicInteger(100);
    private static AtomicStampedReference atomicStampedReference = new AtomicStampedReference(100,1);

    public static void main(String[] args) throws InterruptedException {

        // AtomicInteger
        Thread at1 = new Thread(new Runnable() {
            @Override
            public void run() {
                atomicInteger.compareAndSet(100,110);
                atomicInteger.compareAndSet(110,100);
            }
        });

        Thread at2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    TimeUnit.SECONDS.sleep(2);      // at1,执行完
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("AtomicInteger:" + atomicInteger.compareAndSet(100,120));
            }
        });

        at1.start();
        at2.start();

        at1.join();
        at2.join();

        // AtomicStampedReference

        Thread tsf1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    //让 tsf2先获取stamp,导致预期时间戳不一致
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 预期引用:100,更新后的引用:110,预期标识getStamp() 更新后的标识getStamp() + 1
                atomicStampedReference.compareAndSet(100,110,atomicStampedReference.getStamp(),atomicStampedReference.getStamp() + 1);
                atomicStampedReference.compareAndSet(110,100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp() + 1);
            }
        });

        Thread tsf2 = new Thread(new Runnable() {
            @Override
            public void run() {
                int stamp = atomicStampedReference.getStamp();

                try {
                    TimeUnit.SECONDS.sleep(2);      //线程tsf1执行完
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("AtomicStampedReference:" +atomicStampedReference.compareAndSet(100,120,stamp,stamp + 1));
            }
        });

        tsf1.start();
        tsf2.start();
    }

}
复制代码

結果:

ABAの問題を実証業績は、のAtomicIntegerを引き起こしABA、問題を解決するAtomicStampedReferenceを使用しています。

おすすめ

転載: juejin.im/post/5d8adcef51882518df00c4d8