CAS CASは理解し、問題を起こしやすいです

Javaの並行性の問題に対処するCAS(比較とスワップ)を比較とスワップ、最も使用される1の方法で、彼の期待に与えられ、単にオブジェクトのVを指定することで、値を変更する必要があり、期待値が同等にある場合値メモリは、その後、置くオブジェクトは、我々が変更したい値に変更され、または変更に失敗しました。

ベストプラクティスを使用してCAS

    私たちは、次のシナリオを見て:

public class Case {
 
    public volatile int n;
 
    public void add() {
        n++;
    }
}

、int型のn個のオブジェクトは、我々は、メモリの視認性を確保するために、揮発性の修飾を使用するが、複数のスレッドが、我々は、add()メソッドを呼び出すと、この方法は、スレッドセーフされていないため、この操作は、n ++アトミック操作ではありません(実際には3つのメインメモリからのnの値を取得するステップ1、2、N + 1、3つの値、nの値は、メインメモリに書き込まれる)上記の3つのステップで、複数のスレッドが同じ時間にアクセスする場合、それは意志nの値を生じさせることが予想よりも小さいです。我々は、上記のこのような状況を解決するにはどうすればよいです

    カーネルモードとユーザとの間のモードの最も一般的な方法は、キーワードを同期addメソッドに追加され、もちろん、我々は、マルチスレッドの安全性を保証することができますが、非常にお勧めします(取得し、ロックを解除するために、同期ヘビー級ロック、する必要はありませ)スイッチング、および以降の同期のcasパフォーマンスの問題を議論します

    別の解決策は、CASが提供する、CAS操作するには、このステップは、アトミック操作されているJavaを使用することで、他のスレッドによってアクセスされることはありません、あなたもロックする必要はありません。そこでは、我々は追加する整数の価値を提供するために見て、パフォーマンスに多くの利点があることのAtomicIntegerの下でJavaクラスのアトミック操作を変更することができます

AtomicInteger底型整数の内部属性値を維持するデータ・クラスを介して動作するように安全ではありません
 

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

図1に示すように、コードが安全でないれる値を変更する後続のダイレクトメモリを容易にする際にメモリオフセットのAtomicIntegerオブジェクトに対するプロパティ値を取得します

クラスのAtomicIntegerに対応して上記の操作のために私たちのn ++、

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




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;
    }

オブジェクトとオブジェクトのオフセットプロパティを使用して、その後、算出したシフト量、現在のメモリの古い値、およびは、古い値に新しい値を追加し、このステップは、この方法により行う-ながら、ことに留意すべきです改正は、成功するまで、

最後の呼び出しがcompareAndSwapIntの間で安全ではありません

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

そのパラメータは現在のオブジェクト、var2である:期待値、VAR5:新しい変更された値属性は、現在のオフセット、VAR4に対するオブジェクト

問題を起こしやすいCAS操作:

1、ABA問題は、V A、修正値V BにメモリA Vの値の中から取得するスレッド2、スレッド2値にメモリ変数から1つの取得スレッド、そして、変数Vのスレッドを回す2変更されたAの値は、スレッドの変数V 1の値を決定するために、この時間は、次に成功変更です。

今回はそこに私は真ん中を変更するかどうか質問かもしれませんが、私は最終的に私は値を期待値に与えた、Integer型を変更するとき、このアイデアはちょうど、真ん中が変更されてもよい、影響はありません。何回くらいの影響力を持っているが、それはリンクリストである場合、影響は重要ではありませんでした

そのようなものとして、単独でリンクされたリスト、A - > B --->ヌル図。

正常に実行されていない、B、head.comareAndSet(A、B)にスタックAのトップを交換したい1は、この時点で追加されたスレッド

この時点でスレッド2、後で正常に設定要素CとDとなったA鎖に、

このとき、1つの操作をスレッドにバックは、コンペア・アンド・スワップは、スタックまたはAを見つけ、その後、正常に実行されますが、次のリストに、この時間に実行しました

 

この場合、唯一のスタック要素B、この場合にはC、Dは、要素を失われます

ABAの問題を解決するためにAtomicStampedReference使用

    内部クラスを維持AtomicStampedReferenceオブジェクトは、オリジナルに基づいてオブジェクト、およびint値を保持する第一の値を変更することを決定し、メモリ内に、各時間を変更し比較すると、(バージョン番号が理解されるであろう)値が同じであり、バージョン番号が同じです
 


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);
        }
    }

これらは、我々は、オブジェクトを作成するときに、内部クラスであり、そしてそれは、あなたが変更を加えるたびに、バージョン番号やタイムスタンプを提供します、我々は、元のタイムスタンプまたはバージョン番号を変更します

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)));
    }

 compareAndSetは、期待、新しい値を渡しますと、オブジェクトのオブジェクトを変更するために、バージョン番号、新しいバージョン番号、および危険な方法を使用することを期待します

private boolean casPair(Pair<V> cmp, Pair<V> val) {
        return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);
    }

CASと同期比較

    jdk1.7では、のAtomicIntegerはgetAndIncrementの方法を実現します

public final int getAndIncrement() {
        for (;;) {
            int current = get();
            int next = current + 1;
            if (compareAndSet(current, next))
                return current;
        }
}

この場合、CASは、ハードウェア原子による操作を完了するために、競争力の場合で動作するように適合されているスレッドが入るので、競合の場合には、ロック、ユーザーモードとカーネルモードのハンドオーバの必要性は、認識しませんロータリー更新操作、およびスレッドによって同期が価値の変更を防ぐためにブロックされている高いCPUシェアながら、私たちはCPUを参照してください、この時間は、スレッドの特定の番号の後に、CASの操作により多くの時間がかかることがありますし、やり方を同期、CPUの彼のシェアを増加させません、


しかし、JDK 1.8でのAtomicIntegerは、最適化され、
 

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

テストした後、これをCASに時間がかかる同期とほぼ同じである、全体的に少なく、同期に時間がかかりCASよりも発見されました


public class CASDemo {
    private final int THREAD_NUM = 800;
    private final int MAX_VALUE = 20000000;
    private AtomicInteger casI = new AtomicInteger(0);
    private int syncI = 0;
    private Object obj = new Object();
 
    public static void main(String[] args) throws Exception {
        CASDemo casDemo = new CASDemo();
        casDemo.casAdd();
        casDemo.syncAdd();
    }
 
    public void casAdd() throws Exception {
        long begin = System.currentTimeMillis();
        Thread[] threads = new Thread[THREAD_NUM];
        for (int i = 0; i < THREAD_NUM; i++) {
            threads[i] = new Thread(new Runnable() {
                @Override
                public void run() {
                    while (casI.get() < MAX_VALUE) {
                        casI.getAndIncrement();
                    }
                }
            });
            threads[i].start();
        }
        for (int i = 0; i < THREAD_NUM; i++) {
            threads[i].join();
        }
        System.out.println("cas costs time :" + (System.currentTimeMillis() - begin));
    }
 
    public void syncAdd() throws Exception {
        long begin = System.currentTimeMillis();
        Thread[] threads = new Thread[THREAD_NUM];
        for (int i = 0; i < THREAD_NUM; i++) {
            threads[i] = new Thread(new Runnable() {
                @Override
                public void run() {
                    while (syncI < MAX_VALUE) {
                        synchronized (obj) {
                            ++syncI;
                        }
                    }
                }
            });
            threads[i].start();
        }
        for (int i = 0; i < THREAD_NUM; i++) {
            threads[i].join();
        }
        System.out.println("synchronized cost:" + (System.currentTimeMillis() - begin));
    }
 
 
}

 

公開された393元の記事 ウォン称賛41 ビュー260 000 +

おすすめ

転載: blog.csdn.net/qq_32440951/article/details/89554654
おすすめ