3

私は AtomicInteger と、その操作がアトミックである方法と、これらのプロパティがマルチスレッドにどのように役立つかについて読んでいました。

同じことをテストするために、次のプログラムを作成しました。

各スレッドは 500 回ループし、スレッドが getNext() を呼び出すたびに一意の番号を取得する必要があるため、セットの最終的なサイズは 1000 になるはずです。

しかし、出力は常に 1000 未満です。ここで何が欠けていますか?

public class Sequencer {

private final AtomicInteger i = new AtomicInteger(0);

public int getNext(){
    return i.incrementAndGet();
}

public static void main(String[] args) {

    final Sequencer seq = new Sequencer();

    final Set<Integer> set = new HashSet<Integer>();

    Thread t1 = new Thread(new Runnable() {
        @Override
        public void run() {
            for (int i=0; i<500; i++)
                set.add(seq.getNext());

        }
    },"T1");
    t1.start();


    Thread t2 = new Thread(new Runnable() {
        @Override
        public void run() {
            for (int i=0; i<500; i++)
                set.add(seq.getNext());

        }
    },"T2");

    t2.start();

    try {
        t1.join();
        t2.join();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    System.out.println(set.size());

}

}

4

4 に答える 4

3

HashSet がスレッドセーフではないことを見逃しています。さらに、セットのプロパティは重複した数値をすべて消去するため、AtomicInteger がスレッドセーフでない場合、テストは失敗します。

代わりにConcurrentLinkedQueueを使用してみてください。

編集: 2回尋ねられたので:同期セットを使用すると機能しますが、アトミッククラスのようなロックフリーアルゴリズムを使用する背後にあるアイデアを破壊します。add上記のコードでセットを同期セットに置き換えると、スレッドは呼び出されるたびにブロックする必要があります。

これにより、実行される作業のみが同期して行われるため、アプリケーションが効果的にシングルスレッドに削減されます。実際には、シングル スレッドよりも遅くなりsynchronizedます。したがって、スレッド化を実際に利用したい場合は、絶対に避けsynchronizedてください。

于 2014-01-25T18:27:42.147 に答える
1

HashSet はスレッド セーフではないため、問題に直面しています。HashSet を厳密に使用する必要がある場合は、Vector またはスレッド セーフなコレクション クラスを使用するか、2 つのスレッドを順番に実行できます。

t1.start();
t1.join();

t2.start();
t2.join();
于 2014-01-25T18:47:02.403 に答える
0

いくつかの回答で述べたように、HashSet がスレッドセーフではないために失敗します。

まず、テストのために、AtomicInteger が実際にスレッドセーフであることを確認してから、テストが失敗した理由を確認しましょう。テストを少し変更します。各スレッドに 1 つずつ、合計 2 つのハッシュセットを使用します。最後に、結合後、2 番目のセットを反復処理して最初のセットに追加することにより、2 番目のセットを最初のセットにマージします。次に、最初のセットでカウントを行います。カウントはあなたが期待するものになります。これは、スレッドセーフではなく、AtomicInteger ではない HashSet であることを証明しています。

それでは、どの側面がスレッドセーフではないかを見てみましょう。add() のみを実行しているため、明らかに add() はスレッドセーフではない操作であり、数値が失われます。数値を失うスレッドセーフではない HashMap add() の疑似コードの例を見てみましょう (これは明らかに実装方法ではありません。スレッドセーフではない方法を 1 つ述べようとしているだけです)。

class HashMap {
 int valueToAdd;
 public add(int valueToAdd) {
   this.valueToAdd = valueToAdd;
   addToBackingStore(this.valueToAdd);
 }
}

複数のスレッドが add() を呼び出し、すべてのスレッドが this.valueToAdd を変更した後に addToBackingStore() に到達した場合、valueToAdd の最終値のみが追加され、他のすべての値は上書きされて失われます。

これに似たことがおそらくあなたのテストで起こったことです。

于 2015-04-01T03:33:18.557 に答える