web-dev-qa-db-ja.com

Javaのvolatileキーワードの最も簡単でわかりやすい例

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

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

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

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

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

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

  • 誰でも揮発性を例で説明できますか? JLSの理論ではありません。
  • 同期の代わりに揮発性がありますか?原子性を達成していますか?
68
tmgr

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

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

揮発性は同期の代替ではありません

Volatileを使用するのは、参照を更新し、それに対して他の操作を実行しない場合のみです。

例:

volatile int i = 0;

public void incrementI(){
   i++;
}

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

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

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

volatileの正しい使用法 volatileのさまざまな使用法について説明しています。 volatileを正しく使用するのは注意が必要です。「疑わしい場合は外してください」と言い、代わりにsynchronizedブロックを使用します。

また:

volatileブロックの代わりに同期ブロックを使用できますが、逆は真ではありません

47
Narendra Pathai

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

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

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

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

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

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

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

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

以下のすべての基準が満たされている場合にのみ、揮発性変数を使用できます。

  • 変数への書き込みはその現在の値に依存しません。または、単一のスレッドのみが値を更新することを保証できます。
  • 変数は、他の状態変数との不変式には関与しません。そして
  • 変数にアクセスしている間、他の理由でロックする必要はありません。

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

これは "Javaの並行性の実践" からの抜粋です。これは、この主題について見つけることができる最高の本です。

27
Sokolov

あなたの例を少し変更しました。次に、keepRunningをvolatileおよびnon volatileメンバーとして使用する例を使用します。

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

volatileキーワードとは?

volatileキーワードは、caching of variablesを防ぎます。

コードを考慮してください、最初volatileなしキーワード

class MyThread extends Thread {
    private boolean running = true;   //non-volatile keyword

    public void run() {
        while (running) {
            System.out.println("hello");
        }
    }

    public void shutdown() {
        running = false;
    }
}

public class Main {

    public static void main(String[] args) {
        MyThread obj = new MyThread();
        obj.start();

        Scanner input = new Scanner(System.in);
        input.nextLine(); 
        obj.shutdown();   
    }    
}

理想的に、このプログラムはprint helloが押されるまでRETURN keyする必要があります。しかし、some machinesでは、変数実行中cachedであり、shutdown()メソッドからその値を変更できないため、infiniteでhelloテキストが出力されます。

したがって、volatileキーワードを使用すると、変数がキャッシュされないguaranteed、つまりrun fineall machinesになります。

private volatile boolean running = true;  //volatile keyword

したがって、volatileキーワードの使用はgoodおよびsafer programming practiceです。

11
Prateek Joshi

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

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

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

println(...)行が入っていても出ていなくても、Intel i7を搭載したMacBook ProのJava6でプログラムがスピンします。

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

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

同期の代わりに揮発性がありますか?それは原子性を達成しますか?

メモリの同期に関しては、volatileは、synchronizedブロックと同じメモリバリアをスローしますが、volatileバリアは単方向または双方向である点が異なります。 volatile読み取りはロードバリアをスローし、書き込みはストアバリアをスローします。 synchronizedブロックは双方向の障壁です。

ただし、atomicityに関しては、答えは「依存します」です。フィールドから値を読み書きする場合、volatileは適切なアトミック性を提供します。ただし、volatileフィールドの増分には、++が実際には3つの操作(読み取り、増分、書き込み)であるという制限があります。その場合、またはより複雑なミューテックスの場合、完全なsynchronizedブロックが必要になる場合があります。

3
Gray

変数がvolatileの場合、変数がキャッシュされず、異なるスレッドが更新された値を見ることが保証されます。ただし、マークを付けないvolatileは反対を保証しません。 volatileは、JVMで長い間壊れていたものの1つであり、常に十分に理解されていませんでした。

3
Jeff Storey

volatileは、JVMとコンパイラに応じて、必ずしも大きな変更を作成するわけではありません。ただし、多くの(エッジ)ケースでは、変数の変更が正しく書き込まれるのではなく、変数の変更が認識されない最適化の違いになる可能性があります。

基本的に、オプティマイザは、レジスタまたはスタックに不揮発性変数を配置することを選択できます。別のスレッドがヒープまたはクラスのプリミティブでそれらを変更した場合、他のスレッドはスタック上でそれを探し続け、古いものになります。

volatileは、そのような最適化が行われないことを保証し、すべての読み取りと書き込みは、ヒープまたはすべてのスレッドがそれを見る別の場所に直接行われます。

2
Andrey Akhmetov

以下の解決策を見つけてください、

この変数の値は、スレッドローカルにキャッシュされることはありません。すべての読み取りと書き込みは、「メインメモリ」に直接送られます。 volatileは、スレッドに毎回元の変数を強制的に更新させます。

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

}

このリンクを参照してください http://Java.dzone.com/articles/Java-volatile-keyword- より明確にするために。

1
Azhaguvel A

Volatileキーワードは、別のスレッドによって変更される可能性があることをJVMに伝えます。各スレッドには独自のスタックがあるため、アクセスできる変数の独自のコピーがあります。スレッドが作成されると、アクセス可能なすべての変数の値が独自のメモリにコピーされます。

public class VolatileTest {
    private static final Logger LOGGER = MyLoggerFactory.getSimplestLogger();

    private static volatile int MY_INT = 0;

    public static void main(String[] args) {
        new ChangeListener().start();
        new ChangeMaker().start();
    }

    static class ChangeListener extends Thread {
        @Override
        public void run() {
            int local_value = MY_INT;
            while ( local_value < 5){
                if( local_value!= MY_INT){
                    LOGGER.log(Level.INFO,"Got Change for MY_INT : {0}", MY_INT);
                     local_value= MY_INT;
                }
            }
        }
    }

    static class ChangeMaker extends Thread{
        @Override
        public void run() {

            int local_value = MY_INT;
            while (MY_INT <5){
                LOGGER.log(Level.INFO, "Incrementing MY_INT to {0}", local_value+1);
                MY_INT = ++local_value;
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) { e.printStackTrace(); }
            }
        }
    }
}

volatileの有無にかかわらずこの例を試してください。

揮発性として宣言されたオブジェクトは、通常、スレッド間で状態情報を通信するために使用されます。揮発性フィールドの値の変更でCPUキャッシュを更新するために発行されます。

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

Volatile変数は、スレッドコンテキストでのみ使用する必要があります。例を参照してください here

0
user2554822

多くの素晴らしい例がありますが、volatileが必要なシナリオがいくつかあるため、それらを支配する具体的な例はありません。

  1. volatileを使用して、すべてのスレッドが変数の最新値をメインメモリから取得するように強制できます。
  2. synchronizationを使用して重要なデータを保護できます
  3. Lock AP​​Iを使用できます
  4. Atomic変数を使用できます

Java volatile examples の詳細を確認してください。

0
Johnny