82

Java のvolatileキーワードについて読んでいて、その理論部分を完全に理解しています。

しかし、私が探しているのは、変数が揮発性でない場合とそうである場合に何が起こるかを示す良い事例です。

以下のコード スニペットは期待どおりに動作しません (ここから取得):

class Test extends Thread {

    boolean keepRunning = true;

    public void run() {
        while (keepRunning) {
        }

        System.out.println("Thread terminated.");
    }

    public static void main(String[] args) throws InterruptedException {
        Test t = new Test();
        t.start();
        Thread.sleep(1000);
        t.keepRunning = false;
        System.out.println("keepRunning set to false.");
    }
}

理想的には、 volatilekeepRunningでない場合、スレッドは無期限に実行し続ける必要があります。ただし、数秒後に停止します。

2 つの基本的な質問があります。

  • 誰かが揮発性を例で説明できますか? JLSの理論ではありません。
  • 揮発性は同期の代用ですか? それは原子性を達成しますか?
4

13 に答える 13

53

揮発性 --> 原子性ではなく可視性を保証します

同期 (ロック) --> 可視性と原子性を保証します (適切に行われた場合)

揮発性は同期の代わりにはなりません

volatile は、参照を更新していて、他の操作を実行していない場合にのみ使用してください。

例:

volatile int i = 0;

public void incrementI(){
   i++;
}

インクリメントは複合操作であるため、同期または AtomicInteger を使用しないとスレッドセーフになりません。

プログラムが無期限に実行されないのはなぜですか?

まあ、それはさまざまな状況に依存します。ほとんどの場合、JVM はコンテンツをフラッシュするほどスマートです。

volatile の正しい使用法では、volatileのさまざまな用途について説明しています。volatile を正しく使用するのは難しいです。「疑わしい場合は除外してください」と言い、代わりに同期ブロックを使用してください。

また:

synchronized ブロックは volatile の代わりに使用できますが、その逆は当てはまりません

于 2013-07-19T14:07:04.783 に答える
28

特定の例: volatile と宣言されていない場合、サーバー JVM はループ内で変更されていない (無限ループに変わる) ため、変数をループから引き上げることができますが、クライアント JVM はそうしませんkeepRunning。そのため、さまざまな結果が表示されます。

volatile 変数に関する一般的な説明は次のとおりです。

フィールドが宣言されるvolatileと、コンパイラとランタイムは、この変数が共有されていること、およびその操作を他のメモリ操作と並べ替えてはならないことを認識します。揮発性変数は、他のプロセッサから隠されているレジスタやキャッシュにはキャッシュされないため、揮発性変数を読み取ると、常に任意のスレッドによる最新の書き込みが返されます

volatile 変数の可視性効果は、volatile 変数自体の値を超えて拡張されます。スレッド A が揮発性変数に書き込み、続いてスレッド B が同じ変数を読み取ると、揮発性変数に書き込む前に A に表示されていたすべての変数の値が、揮発性変数の読み取り後に B に表示されます。

volatile 変数の最も一般的な用途は、完了、中断、またはステータス フラグとしてです。

  volatile boolean flag;
  while (!flag)  {
     // do something untill flag is true
  }

揮発性変数は他の種類の状態情報にも使用できますが、これを試みるときはより注意が必要です。たとえば、volatile のセマンティクスはcount++、変数が単一のスレッドからのみ書き込まれることを保証できない限り、インクリメント操作 ( ) をアトミックにするほど強力ではありません。

ロックは、可視性と原子性の両方を保証できます。volatile 変数は、可視性のみを保証できます。

volatile 変数を使用できるのは、次のすべての基準が満たされている場合のみです。

  • 変数への書き込みは現在の値に依存しません。または、単一のスレッドのみが値を更新するようにすることができます。
  • 変数は、他の状態変数を持つ不変条件には参加しません。と
  • 変数がアクセスされている間は、他の理由でロックする必要はありません。

デバッグのヒント: 開発やテストであっても、JVM を呼び出すときは必ず-serverJVM コマンド ライン スイッチを指定してください。サーバー JVM は、クライアント JVM よりも多くの最適化を実行します。たとえば、ループ内で変更されない変数をループから巻き上げます。開発環境 (クライアント JVM) で動作するように見えるコードが、デプロイメント環境 (サーバー JVM) では機能しなくなる可能性があります。

これは、このテーマに関する最高の本である「Java Concurrency in Practice」からの抜粋です。

于 2013-07-19T14:24:11.510 に答える
17

あなたの例を少し修正しました。次に、keepRunning を揮発性および非揮発性メンバーとして使用する例を使用します。

class TestVolatile extends Thread{
    //volatile
    boolean keepRunning = true;

    public void run() {
        long count=0;
        while (keepRunning) {
            count++;
        }

        System.out.println("Thread terminated." + count);
    }

    public static void main(String[] args) throws InterruptedException {
        TestVolatile t = new TestVolatile();
        t.start();
        Thread.sleep(1000);
        System.out.println("after sleeping in main");
        t.keepRunning = false;
        t.join();
        System.out.println("keepRunning set to " + t.keepRunning);
    }
}
于 2014-01-05T10:02:52.347 に答える
7

理想的には、keepRunning が揮発性でない場合、スレッドは無期限に実行し続ける必要があります。ただし、数秒後に停止します。

シングル プロセッサで実行している場合、またはシステムが非常にビジーである場合、OS がスレッドをスワップ アウトしている可能性があり、これによりいくつかのレベルのキャッシュが無効化されます。をvolatile持たないからといってメモリが共有されないというわけではありませんが、JVM はパフォーマンス上の理由から可能であればメモリを同期しないようにしようとしているため、メモリが更新されない可能性があります。

注意すべきもう 1 つの点はSystem.out.println(...)、基になるPrintStreamものが同期を行って出力の重複を停止するため、同期されていることです。したがって、メインスレッドで「無料で」メモリ同期を取得しています。ただし、これは、読み取りループが更新をまったく確認する理由をまだ説明していません。

行が入っているかprintln(...)出ていないかに関係なく、Intel i7 を搭載した MacBook Pro の Java6 で、プログラムが回転します。

誰かが例で揮発性を説明できますか? JLSの理論ではありません。

あなたの例は良いと思います。System.out.println(...)すべてのステートメントを削除しても機能しない理由がわかりません。わたしにはできる。

volatile は同期の代用ですか? それは原子性を達成しますか?

メモリ同期に関しては、バリアが単方向と双方向で あることを除いてvolatile、ブロックと同じメモリ バリアをスローします。読み取りはロードバリアをスローし、書き込みはストアバリアをスローします。ブロックは、mutex ロックが追加された双方向バリアです。synchronizedvolatilevolatilesynchronized

ただし、に関してはatomicity、答えは「場合による」です。フィールドから値を読み書きする場合は、volatile適切な原子性を提供します。ただし、volatileフィールドのインクリメントには、++実際には読み取り、インクリメント、書き込みの 3 つの操作という制限があります。その場合、またはより複雑なミューテックスの場合、完全なsynchronizedブロックが必要になる場合があります。 複雑なテストと設定のスピンループで問題をAtomicInteger解決します。++

于 2013-07-19T14:04:25.193 に答える
1

Please find the solution below,

The value of this variable will never be cached thread-locally: all reads and writes will go straight to "main memory". The volatile force the thread to update the original variable for each time.

public class VolatileDemo {

    private static volatile int MY_INT = 0;

    public static void main(String[] args) {

        ChangeMaker changeMaker = new ChangeMaker();
        changeMaker.start();

        ChangeListener changeListener = new ChangeListener();
        changeListener.start();

    }

    static class ChangeMaker extends Thread {

        @Override
        public void run() {
            while (MY_INT < 5){
                System.out.println("Incrementing MY_INT "+ ++MY_INT);
                try{
                    Thread.sleep(1000);
                }catch(InterruptedException exception) {
                    exception.printStackTrace();
                }
            }
        }
    }

    static class ChangeListener extends Thread {

        int local_value = MY_INT;

        @Override
        public void run() {
            while ( MY_INT < 5){
                if( local_value!= MY_INT){
                    System.out.println("Got Change for MY_INT "+ MY_INT);
                    local_value = MY_INT;
                }
            }
        }
    }

}

Please refer this link http://java.dzone.com/articles/java-volatile-keyword-0 to get more clarity in it.

于 2014-10-28T13:43:28.187 に答える
0

volatile として宣言されたオブジェクトは、通常、スレッド間で状態情報を通信するために使用されます。CPU キャッシュが確実に更新されるように、つまり、揮発性フィールド、CPU 命令、メモリ バリア (多くの場合メンバーまたはフェンスは、揮発性フィールドの値の変更で CPU キャッシュを更新するために発行されます。

volatile 修飾子は、volatile によって変更された変数が、プログラムの他の部分によって予期せず変更される可能性があることをコンパイラに伝えます。

volatile 変数は、スレッド コンテキストでのみ使用する必要があります。こちらの例をご覧ください

于 2013-10-28T19:49:18.010 に答える