web-dev-qa-db-ja.com

Javaでvolatileキーワードを正確に使用するのはいつですか?

Javaで「volatile」を使用する場合 」と読みましたが、まだ混乱しています。いつ変数をvolatileにマークする必要があるかを知るにはどうすればよいですか?間違っている場合は、必要なもののvolatileを省略したり、必要のないものにvolatileを付けたりするとどうなりますか?マルチスレッドコードでどの変数を揮発性にする必要があるかを判断するときの経験則は何ですか?

85
Ricket

基本的には、複数のスレッドがメンバー変数にアクセスできるようにしたいが、複合原子性を必要としない場合に使用します(これが正しい用語かどうかはわかりません)。

class BadExample {
    private volatile int counter;

    public void hit(){
        /* This operation is in fact two operations:
         * 1) int tmp = this.counter;
         * 2) this.counter = tmp + 1;
         * and is thus broken (counter becomes fewer
         * than the accurate amount).
         */
        counter++;
    }
}

上記は悪い例です。なぜなら、あなたはneed原子性を必要とするからです。

 class BadExampleFixed {
    private int counter;

    public synchronized void hit(){
        /*
         * Only one thread performs action (1), (2) at a time
         * "atomically", in the sense that other threads can not 
         * observe the intermediate state between (1) and (2).
         * Therefore, the counter will be accurate.
         */
        counter++;
    }
}

ここで有効な例に:

 class GoodExample {
    private static volatile int temperature;

    //Called by some other thread than main
    public static void todaysTemperature(int temp){
        // This operation is a single operation, so you 
        // do not need compound atomicity
        temperature = temp;
    }

    public static void main(String[] args) throws Exception{
        while(true){
           Thread.sleep(2000);
           System.out.println("Today's temperature is "+temperature);
        }
    }
}

では、なぜprivate static int temperature?実際には(プログラムが爆破しないなどの意味で)できますが、他のスレッドによるtemperatureへの変更は、メインスレッドに「見える」場合とそうでない場合があります。

基本的に、これはアプリが可能であることを意味します。書き続けるToday's temperature is 0永遠に使用しないvolatile(実際には、値は最終的に表示される傾向があります。ただし、必要です。これは、厄介なバグ(不完全に構築されたオブジェクトなどが原因)につながる可能性があるためです。

volatileを必要としないものにvolatileキーワードを付けても、コードの正確性には影響しません(つまり、動作は変わりません)。パフォーマンスに関しては、JVMの実装に依存します。理論的には、コンパイラは最適化の順序を変更したり、CPUキャッシュを無効にしたりする必要がないため、パフォーマンスがわずかに低下する可能性がありますが、コンパイラはフィールドが複数のスレッドからアクセスできないことを証明し、volatileの効果を削除できますキーワードを完全に作成し、同一の命令にコンパイルします。

編集:
このコメントへの応答:

わかりましたが、なぜ今日の温度を同期させ、温度の同期ゲッターを作成できないのですか

でき、正しく動作します。 volatileでできることはすべてsynchronizedでできますが、その逆はできません。可能であれば、volatileを好む2つの理由があります。

  1. バグの少ない傾向:これはコンテキストに依存しますが、多くの場合、volatileを使用すると、ロックを保持しながらブロックする、デッドロックなどの並行性バグが発生しにくくなります。
  2. パフォーマンスの向上:ほとんどのJVM実装では、volatileのスループットが大幅に向上し、レイテンシが向上します。ただし、ほとんどのアプリケーションでは、その差は問題にはなりません。
107
Enno Shioji

揮発性は、ロックフリーアルゴリズムで最も役立ちます。ロックを使用してその変数にアクセスせず、あるスレッドによる変更を別のスレッドで表示したい場合、または計算が確実に行われるように「後」関係を作成する場合、共有データを保持する変数を揮発性としてマークします適切なタイミングで変更が表示されるようにするために、再度並べ替える必要はありません。

JMM Cookbook は、どの操作を並べ替えることができ、どの操作を並べ替えることができないかを説明します。

14
mdma

volatileキーワードは、揮発性変数の値がスレッドのローカルキャッシュからではなく、常にメインメモリから読み取られることを保証します。

From Java concurrency tutorial

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

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

クエリについて:

変数をいつvolatileにマークする必要があるかを知るにはどうすればよいですか?マルチスレッドコードでどの変数を揮発性にする必要があるかを判断するときの経験則は何ですか?

すべてのリーダースレッドが常に変数の最新の値を取得すると感じる場合は、変数をvolatileとしてマークする必要があります。

変数の値を変更するライタースレッドと変数の値を読み取る複数のリーダースレッドがある場合、volatile修飾子はメモリの一貫性を保証します。

変数を読み書きする複数のスレッドがある場合、volatile修飾子だけではメモリの一貫性は保証されません。コードをsynchronizeするか、高レベルを使用する必要があります concurrencyLocksConcurrent CollectionsAtomic variablesなど.

関連するSEの質問/記事:

Java docs の可変変数の説明)==

Javaでのvolatileとsynchronizedの違い

javarevisited 記事

6
Ravindra babu

volatileは、マルチスレッド環境で不変オブジェクトを安全に公開するためにも使用できます。

public volatile ImmutableObject fooのようなフィールドを宣言すると、すべてのスレッドが常に現在利用可能なインスタンス参照を参照するようになります。

このトピックの詳細については、「 Javaの同時実行性 」を参照してください。

5

私の知る限り、実際にトップ投票の回答に記載されている例に同意しません[〜#〜] not [〜#〜] Javaメモリに従って揮発性セマンティクスを適切に示しますモデル。揮発性には、より複雑なセマンティクスがあります。

提供された例では、メインスレッドは、他のスレッドがスケジュールされない場合に温度を更新することになっている別のスレッドが実行されている場合でも、「今日の温度は0」と表示し続けます。

揮発性のセマンティクスを説明するより良い方法は、2つの変数を使用することです。

簡単にするために、2つの変数を更新する唯一の方法は、メソッド"setTemperatures"を使用することであると想定します。

簡単にするために、メインスレッドとスレッド2の2つのスレッドのみが実行されていると仮定します。

//volatile variable
private static volatile int temperature; 
//any other variable, could be volatile or not volatile doesnt matter.
private static int yesterdaysTemperature
//Called by other thread(s)
public static void setTemperatures(int temp, int yestemp){
    //thread updates yesterday's temperature
    yesterdaysTemperature = yestemp;
    //thread updates today's temperature. 
    //This instruction can NOT be moved above the previous instruction for optimization.
    temperature = temp;
   }

最後の2つの割り当て命令は[〜#〜] not [〜#〜]であり、コンパイラ、ランタイム、またはハードウェアのいずれかによって最適化のために並べ替えられます。

public static void main(String[] args) throws Exception{
    while(true){
       Thread.sleep(2000);
       System.out.println("Today's temperature is "+temperature); 
       System.out.println("Yesterday's temperature was "+yesterdaysTemperature );
 }
}

メインスレッドが(印刷の過程で)揮発性の可変温度を読み取ると、

1)書き込み中のスレッドの数、更新する方法、同期するかどうかに関係なく、この揮発性変数の直近に書き込まれた値が表示されることが保証されます。

2)メインスレッドのsystem.outステートメントが実行される場合、afterスレッド2がステートメントtemperature = tempを実行した瞬間、昨日の温度と今日の温度の両方がステートメントtemperature = tempを実行したときに、スレッド2で設定された値を出力することが保証されています。

この状況は[〜#〜] lot [〜#〜]を取得しますa)複数のスレッドが実行されている場合b)昨日の温度を更新できるsetTemperaturesメソッド以外のメソッドがあるこれらの他のスレッドによってアクティブに呼び出されている今日の温度。 Java Memory Modelがvolatileのセマンティクスをどのように記述するかに基づいて、意味を分析するには適切なサイズの記事が必要だと思います。

要するに、同期のためにvolatileのみを使用しようとすることは非常に危険であり、メソッドの同期に固執する方がよいでしょう。

3
programmerravi

http://mindprod.com/jgloss/volatile.html

「volatileキーワードは、他のスレッドによって同時に変更される可能性のある変数で使用されます。」

「他のスレッドはローカル変数を見ることができないため、ローカル変数を揮発性としてマークする必要はありません。異なるスレッドからの変数への変更を調整するために同期する必要があります。

2
KristofMols

voltalieは値を変更し続けます。この変数の値はスレッドローカルにキャッシュされません。すべての読み取りと書き込みは「メインメモリ」に直接送られます。つまり、Javaコンパイラと値をキャッシュしないスレッドこの変数を使用し、常にメインメモリから読み取ります。

2
Akshay