8

最近、volatile キーワードのこの奇妙な動作を経験しました。私の知る限りでは、

  1. volatile キーワードが変数に適用され、あるスレッドによって変数のデータに対して行われた変更が他のスレッドに反映されます。

  2. volatile キーワードは、スレッドでのデータのキャッシュを防ぎます。

私は小さなテストをしました........

  1. count という整数変数を使用し、それに対して volatile キーワードを使用しました。

  2. 次に、変数値を 10000 にインクリメントする 2 つの異なるスレッドを作成したため、最終的な結果は 20000 になるはずです。

  3. しかし、常にそうであるとは限りません.volatileキーワードを使用すると、一貫して20000を取得できませんが、18534、15000など....そして時には20000になります.

  4. しかし、同期キーワードを使用している間、それはうまくいきました.なぜ....??

volatile キーワードのこの動作を説明してください。

volatile キーワードと synchronzied キーワードを含むコードを投稿しています。

以下のコードは、変数カウントの volatile キーワードで一貫性のない動作をします。

public class SynVsVol implements Runnable{

    volatile int  count = 0;

    public void go(){

        for (int i=0 ; i<10000 ; i++){
             count = count + 1;
         }
    }

    @Override
    public void run() {
        go();
    }

    public static void main(String[] args){

        SynVsVol s = new SynVsVol();
        Thread t1 = new Thread(s);
        Thread t2 = new Thread(s);
        t1.start();
        t2.start();

        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println("Total Count Value: "+s.count);
    }
}

次のコードは、メソッド go() の synchronized キーワードで完全に動作します。

public class SynVsVol implements Runnable{

    int  count = 0;

    public synchronized void go(){
        for (int i=0 ; i<10000 ; i++){
             count = count + 1;
         }
    }

    @Override
    public void run() {
        go();
    }

    public static void main(String[] args){

        SynVsVol s = new SynVsVol();
        Thread t1 = new Thread(s);
        Thread t2 = new Thread(s);
        t1.start();
        t2.start();

        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println("Total Count Value: "+s.count);
    }
}
4

4 に答える 4

12

count = count + 1アトミックではありません。次の 3 つの手順があります。

  1. 変数の現在の値を読み取る
  2. 値を増やす
  3. 新しい値を変数に書き戻す

これら 3 つのステップが織り交ぜられ、実行パスが異なり、結果として正しくない値になります。AtomicInteger.incrementAndGet()synchronized キーワードを避けたい場合は、代わりに使用してください。

したがって、volatileキーワードは、説明したとおりに機能しますが、それは個別の操作にのみ適用され、3 つの操作すべてにまとめて適用されるわけではありません。

于 2012-07-01T10:54:25.187 に答える
7

volatileキーワードは同期プリミティブではありません。スレッドでの値のキャッシュを防止するだけで、2 つのスレッドが同じ値を同時に変更して書き戻すことを防止するものではありません。

2 つのスレッドがカウンターをインクリメントする必要がある時点に達したとします。カウンターは現在 5 に設定されています。両方のスレッドが 5 を見て、そこから 6 を作り、それをカウンターに書き戻します。カウンターが でなかった場合volatile、両方のスレッドは、値が 6 であることを知っていると想定し、次の読み取りをスキップできます。ただし、これは揮発性であるため、どちらも 6 を読み返し、インクリメントを続けます。スレッドがロックステップで進行していないため、出力に 10000 とは異なる値が表示される場合がありますが、20000 が表示される可能性はほとんどありません。

于 2012-07-01T10:54:31.570 に答える
4

変数が含まれているという事実は、volatileそれが関与するすべての操作がアトミックであることを意味するわけではありません。たとえば、次の行は次のSynVsVol.Goとおりです。

count = count + 1;

最初にcount読み取られ、次にインクリメントされ、結果が書き戻されます。他のスレッドが同時にそれを実行する場合、結果はコマンドのインターリーブに依存します。

これで、 を追加すると、アトミックsyncronizedに実行されます。SynVsVol.Goつまり、インクリメントは 1 つのスレッドによって全体として行われ、countそれが完了するまで他のスレッドは変更できません。

最後に、同期化されたブロック内でのみ変更されるメンバー変数のキャッシュは、はるかに簡単です。コンパイラは、モニターが取得されたときにそれらの値を読み取り、それをレジスターにキャッシュし、そのレジスターですべての変更を行い、モニターが解放されたときに最終的にメインメモリーにフラッシュバックできます。これは、同期ブロックを呼び出した場合waitや、他のスレッドnotifyが次のような場合にも当てはまります。キャッシュされたメンバー変数が同期され、プログラムの一貫性が保たれます。メンバー変数が volatile として宣言されていない場合でも、これは保証されます。

同期により、同期ブロックの前または最中のスレッドによるメモリ書き込みが、同じモニターで同期する他のスレッドに予測可能な方法で表示されるようになります。

于 2012-07-01T10:53:42.117 に答える
3

volatileあなたのコードは、読み取りとインクリメント操作をアトミックとして扱うため、壊れていますが、そうではありません。コードにはデータ競合は含まれていませんが、int.

于 2012-07-01T10:53:55.580 に答える