web-dev-qa-db-ja.com

volatileキーワードは何に役立ちますか

今日の仕事で、私はJavaのvolatileキーワードに出会いました。あまりよく知らないので、私はこの説明を見つけました:

Javaの理論と実践:ボラティリティの管理

その記事が問題のキーワードを説明している詳細を考えると、あなたはそれをそれを使ったことがありますか、またはあなたがこのキーワードを正しい方法で使うことができるケースを見たことがありますか?

598
Richard

volatileはメモリ可視性のための意味論を持っています。基本的に、volatileフィールドの値は書き込み操作が完了した後にすべてのリーダー(特に他のスレッド)に見えるようになります。 volatileがないと、読者は更新されていない値を見ることができます。

あなたの質問に答えるには:はい、あるコードがループを継続するかどうかを制御するためにvolatile変数を使用します。ループはvolatile値をテストし、それがtrueである場合は続行します。 "stop"メソッドを呼び出すことで条件をfalseに設定できます。ループはfalseを見て、stopメソッドが実行を完了した後に値をテストすると終了します。

私が強くお勧めしている本 " Java Concurrency in Practice "は、volatileの良い説明を提供しています。この本は、質問で参照されているIBMの記事を書いたのと同じ人が書いています(実際、彼は自分の本をその記事の最後に引用しています)。私のvolatileの使用は、彼の記事が「パターン1のステータスフラグ」と呼んでいるものです。

volatile が内部でどのように機能するかについてもっと知りたい場合は、 Javaメモリモデルを読んでください このレベルを超えたい場合は、 Hennessy&Patterson のような優れたコンピュータアーキテクチャの本を調べて、キャッシュの一貫性とキャッシュの一貫性について読んでください。

693
Greg Mattes

「……volatile修飾子は、フィールドを読むすべてのスレッドが最新の値を見ることを保証します。」 - Josh Bloch

volatileを使うことを考えているのなら、アトミックな振る舞いを扱うパッケージ Java.util.concurrent を読んでください。

シングルトンパターン に関するウィキペディアの投稿では、使用中の揮発性が示されています。

162
Ande TURNER

volatilename __:についての重要なポイント

  1. Javaキーワードsynchronizedname__およびvolatilename__とlocksを使用して、Javaでの同期が可能です。
  2. Javaでは、synchronizedname__変数を持つことはできません。変数とともにsynchronizedname__キーワードを使用することは不正であり、コンパイルエラーになります。 Javaでsynchronizedname__変数を使用する代わりに、Javaのvolatilename__変数を使用することができます。これはJVMスレッドにメインメモリからvolatilename__変数の値を読み取り、ローカルにキャッシュしないように指示します。
  3. 変数が複数のスレッド間で共有されていない場合は、volatilename__キーワードを使用する必要はありません。

ソース

volatilename __:の使用例

public class Singleton {
    private static volatile Singleton _instance; // volatile variable
    public static Singleton getInstance() {
        if (_instance == null) {
            synchronized (Singleton.class) {
                if (_instance == null)
                    _instance = new Singleton();
            }
        }
        return _instance;
    }
}

最初のリクエストが来た時点で、インスタンスを遅延して作成しています。

_instance変数をvolatilename__にしないと、Singletonname__のインスタンスを作成しているスレッドは他のスレッドと通信できません。そのため、スレッドAがシングルトンインスタンスを作成していて、作成直後にCPUが破損すると、他のすべてのスレッドは_instanceの値をnullではないと見なすことができず、まだnullが割り当てられていると考えられます。

なぜこれが起こるのですか?リーダースレッドはロックを行わず、ライタースレッドが同期ブロックから出るまでメモリは同期されず、_instanceの値はメインメモリで更新されません。 JavaのVolatileキーワードを使用すると、これはJava自体によって処理され、そのような更新はすべてのリーダースレッドによって表示されます。

結論volatilename__キーワードは、スレッド間でメモリの内容をやり取りするためにも使用されます。

volatileなしの使用例:

public class Singleton{    
    private static Singleton _instance;   //without volatile variable
    public static Singleton getInstance(){   
          if(_instance == null){  
              synchronized(Singleton.class){  
               if(_instance == null) _instance = new Singleton(); 
      } 
     }   
    return _instance;  
    }

上記のコードはスレッドセーフではありません。 (パフォーマンス上の理由で)同期ブロック内でinstanceの値をもう一度チェックしますが、JITコンパイラは、コンストラクタが実行を終了する前にインスタンスへの参照が設定されるようにバイトコードを並べ替えることができます。つまり、メソッドgetInstance()は、完全に初期化されていない可能性があるオブジェクトを返します。コードをスレッドセーフにするために、Java 5以降のインスタンス変数にキーワードvolatileを使用できます。揮発性としてマークされた変数は、オブジェクトのコンストラクタがその実行を完全に終了した後にのみ他のスレッドから見えるようになります。
出典

enter image description here

volatileJavaでの使用方法

フェイルファストイテレータは、リストオブジェクトのvolatilename__カウンタを使用して通常実装されます。

  • リストが更新されると、カウンタが増加します。
  • Iteratorname__が作成されると、カウンターの現在の値がIteratorname__オブジェクトに埋め込まれます。
  • Iteratorname__操作が実行されると、メソッドは2つのカウンター値を比較し、異なる場合はConcurrentModificationExceptionname__をスローします。

フェイルセーフイテレータの実装は通常軽量です。それらは通常、特定のリスト実装のデータ構造のプロパティに依存します。一般的なパターンはありません。

104
Premraj

volatileはスレッドを止めるのにとても便利です。

あなたがあなた自身のスレッドを書くべきであるということではなく、Java 1.6にはたくさんのNiceスレッドプールがあります。しかし、スレッドが必要だと確信している場合は、それを止める方法を知っておく必要があります。

私がスレッドに使用するパターンは次のとおりです。

public class Foo extends Thread {
  private volatile boolean close = false;
  public void run() {
    while(!close) {
      // do work
    }
  }
  public void close() {
    close = true;
    // interrupt here if needed
  }
}

同期が不要なことに注目してください。

49
Pyrolistical

volatileを使用する一般的な例の1つは、スレッドを終了するためのフラグとしてvolatile boolean変数を使用することです。スレッドを開始したときに、別のスレッドから安全にそれを中断できるようにしたい場合は、スレッドに定期的にフラグをチェックさせることができます。停止するには、フラグをtrueに設定してください。フラグをvolatileにすることで、それをチェックしているスレッドが、synchronizedブロックを使用しなくても、次回チェック時に設定されることを保証できます。

30
Dave L.

volatileキーワードで宣言された変数には、特別な2つの主な性質があります。

  1. Volatile変数がある場合、スレッドによってコンピューターの(マイクロプロセッサ)キャッシュメモリにキャッシュすることはできません。アクセスは常にメインメモリから発生しました。

  2. 書き込み操作が揮発性変数で行われ、突然読み取り操作が要求された場合、書き込み操作は、読み取り操作の前に終了することが保証されています。

上記の2つの品質は、

  • 揮発性変数を読み取るすべてのスレッドは、必ず最新の値を読み取ります。キャッシュされた値はそれを汚染できないためです。また、読み取り要求は、現在の書き込み操作の完了後にのみ許可されます。

一方、

  • 前述の#2をさらに調査すると、volatileキーワードは、'n'の数を持つ共有変数を維持する理想的な方法であることがわかります。読み取りスレッドと1つの書き込みスレッドのみにアクセスします。 volatileキーワードを追加したら、完了です。スレッドセーフに関するその他のオーバーヘッドはありません。

逆に、

を使用できないのみvolatileキーワードを使用して、にアクセスする複数の書き込みスレッドを持つ共有変数を満たす

15

ロングダブル変数型の読み書き操作の扱いについては誰も言及していません。読み取りおよび書き込みは、参照変数およびほとんどのプリミティブ変数に対するアトミック操作です。ただし、アトミック操作にするにはvolatileキーワードを使用する必要があります。 @ link

12

はい、変更可能な変数に複数のスレッドからアクセスする場合は、volatileを使用する必要があります。それはあまり一般的ではありません。通常は、アトミック操作を複数回実行する必要があるため(変更する前に変数の状態を確認するなど)、代わりにsynchronizedブロックを使用します。

12
ykaganovich

私の意見では、volatileキーワードが使用されているスレッドを停止する以外の2つの重要なシナリオは次のとおりです。

  1. 二重チェックされたロック機構 。シングルトンデザインパターンでよく使われます。この場合、シングルトンオブジェクトはvolatileとして宣言する必要があります。
  2. スプリアスウェイクアップ 。通知呼び出しが発行されていなくても、スレッドが待機呼び出しからウェイクアップすることがあります。この振る舞いは、サプリメンタルウェイクアップと呼ばれます。これは、条件変数(ブールフラグ)を使用して対処できます。フラグがtrueである限り、wait()呼び出しをwhileループに入れます。そのため、notify/notifyall以外の理由でスレッドがwait callからウェイクアップした場合、flagがまだ真であるため、再度waitを呼び出します。 notifyを呼び出す前に、このフラグをtrueに設定してください。この場合、ブールフラグはvolatileとして宣言されます。
9
Aniket Thakur

volatileは、すべてのスレッドが、それ自体であっても増加していることを保証するだけです。例えば、カウンターは変数の同じ面を同時に見ます。同期やアトミックなどの代わりに使用されるのではなく、読み取りを完全に同期させます。他のJavaキーワードと比較しないでください。以下の例が示すように、volatile変数操作もアトミックです。それらは失敗するか、または同時に成功します。

package io.netty.example.telnet;

import Java.util.ArrayList;
import Java.util.List;

public class Main {

    public static volatile  int a = 0;
    public static void main(String args[]) throws InterruptedException{

        List<Thread> list = new  ArrayList<Thread>();
        for(int i = 0 ; i<11 ;i++){
            list.add(new Pojo());
        }

        for (Thread thread : list) {
            thread.start();
        }

        Thread.sleep(20000);
        System.out.println(a);
    }
}
class Pojo extends Thread{
    int a = 10001;
    public void run() {
        while(a-->0){
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Main.a++;
            System.out.println("a = "+Main.a);
        }
    }
}

変動してもしなくても結果は常に異なります。しかし、以下のようにAtomicIntegerを使用すると、結果は常に同じになります。これはsynchronizedでも同じです。

    package io.netty.example.telnet;

    import Java.util.ArrayList;
    import Java.util.List;
    import Java.util.concurrent.atomic.AtomicInteger;

    public class Main {

        public static volatile  AtomicInteger a = new AtomicInteger(0);
        public static void main(String args[]) throws InterruptedException{

            List<Thread> list = new  ArrayList<Thread>();
            for(int i = 0 ; i<11 ;i++){
                list.add(new Pojo());
            }

            for (Thread thread : list) {
                thread.start();
            }

            Thread.sleep(20000);
            System.out.println(a.get());

        }
    }
    class Pojo extends Thread{
        int a = 10001;
        public void run() {
            while(a-->0){
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Main.a.incrementAndGet();
                System.out.println("a = "+Main.a);
            }
        }
    }
5
fatih tekin

マルチスレッドアプリケーションを開発している場合は、 'volatile'キーワード、または 'synchronized'などの同時実行制御ツールやテクニックを自由に使用できます。そのようなアプリケーションの例はデスクトップアプリケーションです。

アプリケーションサーバー(Tomcat、JBoss AS、Glassfishな​​ど)にデプロイされるアプリケーションを開発している場合は、アプリケーションサーバーによって既に対処されているので、同時並行性制御を自分で処理する必要はありません。実際、私が正しく記憶しているとすれば、Java EE標準はサーブレットとEJBでの同時実行制御を禁止しています。このようなアプリでは、シングルトンオブジェクトを実装している場合にのみ並行性制御を行います。 Springのようなframeworkdを使ってあなたのコンポーネントを編むならば、これはすでにすでに対処されています。

そのため、アプリケーションがWebアプリケーションで、SpringやEJBなどのIoCフレームワークを使用しているほとんどのJava開発では、 'volatile'を使用する必要はありません。

5
Rudi Adianto

はい、私はかなりそれを使用します - それはマルチスレッドコードのために非常に役に立ちます。あなたが指摘した記事は良いものです。ただし、2つの重要な点があります。

  1. 揮発性成分は、揮発性成分が何をしているのか、また同期とはどう違うのかを完全に理解している場合にのみ使用してください。多くの状況では、揮発性についての理解を深めることで、同期が有効な唯一の選択肢であることが明らかになる場合が多いため、表面的には揮発性は同期よりも単純で高性能な代替手段であるように見えます。
  2. 同期は機能しますが、volatileは実際には多くの古いJVMでは機能しません。私は、異なるJVMでのさまざまなレベルのサポートを参照している文書を見たことを覚えていますが、残念ながら今それを見つけることができません。 1.5より前のJavaを使用している場合、またはプログラムを実行する予定のJVMを制御できない場合は、必ずそれを調べてください。
4
MB.

もちろん。 (JavaだけでなくC#でも同様です。)与えられたプラットフォームでアトミックな操作であることが保証されている値(intやbooleanなど)を取得または設定する必要がある場合がありますが、必須ではありません。スレッドロックのオーバーヘッドvolatileキーワードを使用すると、値を読み取ったときに、現在のの値を取得し、書き込みによって廃止されたばかりのキャッシュ値ではないことを確認できます。別のスレッドで。

3
dgvid

揮発性フィールドにアクセスするすべてのスレッドは、(潜在的に)キャッシュ値を使用する代わりに、続行する前に現在の値を読み取ります。

メンバー変数のみが揮発性または一時変数になります。

3
tstuber

Volatileキーワードの使い方は2つあります。

  1. JVMがレジスタから値を読み取らないようにし(キャッシュと見なします)、その値がメモリから読み取られるようにします。
  2. メモリの不整合エラーのリスクを軽減します。

JVMがレジスタ内の値を読み取らないようにし、その値をメモリから読み取らせます。

busy flag は、デバイスがビジーでフラグがロックによって保護されていない間、スレッドが続行しないようにするために使用されます。

while (busy) {
    /* do something else */
}

テストスレッドは、別のスレッドが busyフラグ をオフにしても続行します。

busy = 0;

ただし、テストスレッドではbusyが頻繁にアクセスされるため、JVMはbusyの値をレジスタに格納してテストを最適化し、テストのたびにメモリのbusyの値を読み取らずにレジスタの内容をテストします。テストスレッドはビジー変更を見ることはなく、他のスレッドはメモリ内のビジーの値を変更するだけで、デッドロックが発生します。 busyフラグ をvolatileとして宣言すると、各テストの前にその値が強制的に読み取られます。

メモリの一貫性エラーのリスクを軽減します。

揮発性変数を使用すると、メモリ一貫性エラーのリスクが軽減されます。これは、揮発性変数への書き込みによって、その同じ変数の後続の読み取りと "起こる前" の関係が確立されるためです。これは、volatile変数への変更は他のスレッドからは常に見えることを意味します。

メモリの一貫性エラーなしで読み書きする技術はatomic actionと呼ばれます。

アトミックアクションは、一度に効果的に発生するアクションです。アトミックアクションは途中で止まることはできません。それは完全に起こるか、あるいはまったく起こりません。アトミックアクションの副作用は、そのアクションが完了するまで表示されません。

以下はアトミックであると指定できるアクションです。

  • 読み取りと書き込みは、参照変数とほとんどのプリミティブ変数(longとdoubleを除くすべての型)にとってアトミックです。
  • 読み書きは、volatileと宣言されたすべての変数(アトミック変数とダブル変数を含む)に対してアトミックです。

乾杯!

2
dheeran

揮発性は以下を行います。

1>異なるスレッドによる揮発性変数の読み書きは、スレッド自身のキャッシュやCPUレジスタからではなく、常にメモリから行われます。そのため、各スレッドは常に最新の値を扱います。 2> 2つの異なるスレッドが同じインスタンスまたは静的変数をヒープ内で使用している場合、他の人の動作が順不同であると見なされることがあります。これについてはjeremy mansonのブログを参照してください。しかし、volatileはここで役立ちます。

次の完全に実行されるコードは、synchronizedキーワードを使用せずに、いくつかのスレッドを事前定義された順序で実行し、出力を印刷する方法を示しています。

thread 0 prints 0
thread 1 prints 1
thread 2 prints 2
thread 3 prints 3
thread 0 prints 0
thread 1 prints 1
thread 2 prints 2
thread 3 prints 3
thread 0 prints 0
thread 1 prints 1
thread 2 prints 2
thread 3 prints 3

これを達成するために、我々は以下の本格的な実行コードを使用するかもしれません。

public class Solution {
    static volatile int counter = 0;
    static int print = 0;
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Thread[] ths = new Thread[4];
        for (int i = 0; i < ths.length; i++) {
            ths[i] = new Thread(new MyRunnable(i, ths.length));
            ths[i].start();
        }
    }
    static class MyRunnable implements Runnable {
        final int thID;
        final int total;
        public MyRunnable(int id, int total) {
            thID = id;
            this.total = total;
        }
        @Override
        public void run() {
            // TODO Auto-generated method stub
            while (true) {
                if (thID == counter) {
                    System.out.println("thread " + thID + " prints " + print);
                    print++;
                    if (print == total)
                        print = 0;
                    counter++;
                    if (counter == total)
                        counter = 0;
                } else {
                    try {
                        Thread.sleep(30);
                    } catch (InterruptedException e) {
                        // log it
                    }
                }
            }
        }
    }
}

以下のgithubリンクはreadmeを持っています、それは適切な説明を与えます。 https://github.com/sankar4git/volatile_thread_ordering

2
sankar banerjee

揮発性変数は軽量同期です。すべてのスレッド間で最新のデータを可視化することが要件であり、原子性が損なわれる可能性がある場合は、そのような状況ではVolatile Variablesを選択する必要があります。揮発性変数に対する読み込みは、レジスタにも他のプロセッサが見ることができないキャッシュにもキャッシュされないため、常に任意のスレッドによって行われた最新の書き込みを返します。揮発性はロックフリーです。シナリオが上記の基準を満たす場合は、volatileを使用します。

2
Neha Vari

Oracleのドキュメント のページ から、メモリの一貫性の問題を解決するためにvolatile変数が必要になります。

揮発性変数への書き込みは、その同じ変数の後続の読み取りとの間にビフォアビフォア関係を確立するため、揮発性変数を使用すると、メモリの一貫性エラーのリスクが軽減されます。

これは、volatile変数への変更は他のスレッドからは常に見えることを意味します。それはまた、スレッドが揮発性変数を読み込むとき、それがvolatileへの最新の変更だけではなく、変更を導いたコードの副作用も見ることを意味します。

Peter Parker answerで説明されているように、volatile修飾子がない場合、各スレッドのスタックは独自の変数のコピーを持つことができます。変数をvolatileにすることで、メモリの一貫性の問題が修正されました。

理解を深めるために jenkov チュートリアルページをご覧ください。

Volatileを使用するためのvolatileおよびユースケースに関する詳細については、関連するSE質問をご覧ください。

Javaにおけるvolatileと同期の違い

実用的な使用例

Java.text.SimpleDateFormat("HH-mm-ss")のように特定のフォーマットで現在時刻を表示する必要があるスレッドがたくさんあります。 Yonは1つのクラスを持つことができ、それは現在の時間をSimpleDateFormatに変換し、1秒ごとに変数を更新します。他のすべてのスレッドは、この揮発性変数を使用して現在の時刻をログファイルに出力することができます。

1
Ravindra babu

以下はvolatileの要件を示すための非常に単純なコードです。

// Code to prove importance of 'volatile' when state of one thread is being mutated from another thread.
// Try running this class with and without 'volatile' for 'state' property of Task class.
public class VolatileTest {
    public static void main(String[] a) throws Exception {
        Task task = new Task();
        new Thread(task).start();

        Thread.sleep(500);
        long stoppedOn = System.nanoTime();

        task.stop(); // -----> do this to stop the thread

        System.out.println("Stopping on: " + stoppedOn);
    }
}

class Task implements Runnable {
    // Try running with and without 'volatile' here
    private volatile boolean state = true;
    private int i = 0;

    public void stop() {
        state = false;
    } 

    @Override
    public void run() {
        while(state) {
            i++;
        }
        System.out.println(i + "> Stopped on: " + System.nanoTime());
    }
}

volatileが使用されていない場合:で停止しました:xxx」というメッセージが表示されても表示されません'停止中:xxx'、そしてプログラムは実行を継続します。

Stopping on: 1895303906650500

volatileを使用した場合:すぐに '停止:xxx'が表示されます。

Stopping on: 1895285647980000
324565439> Stopped on: 1895285648087300
0
manikanta

揮発性キーを変数と一緒に使用すると、この変数を読み取るスレッドが同じ値を認識するようになります。変数を読み書きするスレッドが複数ある場合は、変数をvolatileにするだけでは不十分で、データが破損します。画像スレッドは同じ値を読み取りましたが、それぞれがいくつかの変更を行った(たとえば、カウンタをインクリメントした)場合、メモリに書き戻すときにデータの整合性が破られます。そのため、変数を同期させる必要があります(異なる方法が可能です)。

変更が1つのスレッドによって行われ、他のスレッドがこの値を読み取るだけでよい場合は、volatileが適しています。

0
Java Main

Javaアプリケーションでスレッドを並行して実行することによって、揮発性変数が非同期的に変更されます。現在「メイン」メモリに保持されている値とは異なる変数のローカルコピーを持つことはできません。事実上、volatileと宣言された変数は、すべてのスレッド間でそのデータを同期させる必要があります。そのため、任意のスレッドでその変数にアクセスまたは更新すると、他のすべてのスレッドはすぐに同じ値になります。もちろん、揮発性変数は「プレーン」変数よりもアクセスと更新のオーバーヘッドが高い可能性があります。スレッドが独自のデータコピーを持つことができる理由は効率が良いからです。

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

参考のために、これを参照してください http://techno-terminal.blogspot.in/2015/11/what-are-volatile-variables.html

0
satish

揮発性変数は、更新されるとメイン共有キャッシュ行の即時更新(フラッシュ)に基本的に使用されるため、変更はすべてのワーカースレッドに即座に反映されます。

0
Niyaz Ahamad

私は Jenkovの説明が好きです

Javavolatileキーワードは、「メインメモリに格納されている」としてJava変数をマークするために使用されます。もっと正確に言うと、揮発性変数のすべての読み取りはCPUキャッシュからではなく、コンピューターのメインメモリーから読み取られ、揮発性変数へのすべての書き込みは、CPUキャッシュだけでなくメインメモリーにも書き込まれるということです。 。

実際、Java 5以降、volatileキーワードは、volatile変数がメインメモリに対して読み書きされることだけでは保証しません。

これはいわゆる「ビフォアビフォアギャランティ」という拡張された可視性保証です。

volatileのパフォーマンス上の考慮事項

揮発性変数を読み書きすると、変数はメインメモリに読み書きされます。メインメモリへの読み書きは、CPUキャッシュへのアクセスよりもコストがかかります。揮発性変数にアクセスすると、通常のパフォーマンス向上手法である命令の並べ替えも妨げられます。したがって、変数の可視性を強制する必要がある場合にのみ、揮発性変数を使用してください。

0
yoAlex5