volatile キーワードは何に役立ちますか
-
01-07-2019 - |
質問
今日仕事中に出会ったのが、 volatile
Javaのキーワード。あまり詳しくないので、次のような説明を見つけました。
その記事で問題のキーワードが詳しく説明されていますが、あなたはそのキーワードを使用したことがありますか、またはこのキーワードを正しい方法で使用できる例を見たことがありますか?
解決
volatile
メモリの可視性に関するセマンティクスを備えています。基本的に、a の値は、 volatile
フィールドは、書き込み操作が完了した後、すべてのリーダー (特に他のスレッド) に表示されます。それなし volatile
, 、読者には更新されていない値が表示される可能性があります。
あなたの質問に答えるには:はい、私は volatile
一部のコードがループを継続するかどうかを制御する変数。ループは以下をテストします volatile
値であり、その場合は続行します true
. 。条件として設定できるのは、 false
「stop」メソッドを呼び出すことによって。ループが見るのは false
stop メソッドの実行が完了した後に値をテストすると終了します。
本「実際の Java 同時実行性」がよく解説されており、とてもお勧めです。 volatile
. 。この本は、質問で参照されている IBM の記事を書いたのと同じ人によって書かれています (実際、彼はその記事の最後に自分の本を引用しています)。私の使い方 volatile
これは彼の記事で「パターン 1 ステータス フラグ」と呼ばれるものです。
詳しい方法を知りたい場合は、 volatile
ボンネットの下で動作します。詳細をお読みください Java メモリ モデル. 。そのレベルを超えたい場合は、次のような優れたコンピュータ アーキテクチャの本を調べてください。 ヘネシー&パターソン また、キャッシュの一貫性とキャッシュの一貫性についてもお読みください。
他のヒント
「… volatile 修飾子は、フィールドを読み取るスレッドが最後に書き込まれた値を参照することを保証します。」 - ジョシュ・ブロック
ご利用をお考えの方は volatile
, 、パッケージを読んでください java.util.concurrent
これはアトミックな動作を扱います。
に関するウィキペディアの投稿 シングルトンパターン 使用中に揮発性があることを示します。
についての重要な点 volatile
:
- Javaキーワードを使用することでJavaでの同期が可能
synchronized
そしてvolatile
そしてロック。 - Java では、次のことはできません。
synchronized
変数。使用するsynchronized
キーワードと変数の併用は不正であり、コンパイル エラーが発生します。を使用する代わりに、synchronized
Java の変数では、Java を使用できます。volatile
変数。JVM スレッドに次の値を読み取るように指示します。volatile
変数をメインメモリから取得し、ローカルにキャッシュしないでください。 - 変数が複数のスレッド間で共有されていない場合は、
volatile
キーワード。
使用例 volatile
:
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
変数 volatile
次に、インスタンスを作成しているスレッド Singleton
他のスレッドと通信できません。したがって、スレッド A がシングルトン インスタンスを作成しており、作成直後に CPU が破損した場合など、他のすべてのスレッドは の値を確認できなくなります。 _instance
null ではないので、ユーザーはまだ null が割り当てられていると信じます。
なぜこのようなことが起こるのでしょうか?読み取りスレッドはロックを行わず、書き込みスレッドが同期ブロックから出るまで、メモリは同期されず、値は _instance
メインメモリでは更新されません。Java の Volatile キーワードを使用すると、これは Java 自体によって処理され、そのような更新はすべてのリーダー スレッドで表示されます。
結論:
volatile
キーワードは、スレッド間でメモリの内容を通信するためにも使用されます。
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;
}
上記のコードはスレッドセーフではありません。JIT コンパイラーは (パフォーマンス上の理由から) 同期されたブロック内でインスタンスの値を再度チェックしますが、コンストラクターの実行が終了する前にインスタンスへの参照が設定されるようにバイトコードを再配置できます。これは、メソッド getInstance() が完全に初期化されていない可能性のあるオブジェクトを返すことを意味します。コードをスレッドセーフにするために、Java 5 以降ではインスタンス変数にキーワード volatile を使用できます。volatile としてマークされた変数は、オブジェクトのコンストラクターが実行を完全に終了した後でのみ他のスレッドに表示されます。
ソース
volatile
Javaでの使用法:
フェイルファスト反復子は次のとおりです。 通常 を使用して実装 volatile
リストオブジェクトのカウンター。
- リストが更新されると、カウンターがインクリメントされます。
- とき
Iterator
が作成されると、カウンターの現在値が埋め込まれます。Iterator
物体。 - とき
Iterator
操作が実行されると、メソッドは 2 つのカウンタ値を比較し、ConcurrentModificationException
それらが異なる場合。
フェールセーフ反復子の実装は通常、軽量です。これらは通常、特定のリスト実装のデータ構造のプロパティに依存します。一般的なパターンはありません。
volatile はスレッドを停止するのに非常に便利です。
独自のスレッドを作成する必要があるわけではありませんが、Java 1.6 には優れたスレッド プールが多数あります。ただし、スレッドが必要であることが確実な場合は、スレッドを停止する方法を知る必要があります。
私がスレッドに使用するパターンは次のとおりです。
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
}
}
同期が必要ないことに注目してください
一般的な使用例の 1 つ volatile
を使用することです volatile boolean
変数をスレッドを終了するフラグとして使用します。スレッドを開始しており、別のスレッドから安全にスレッドを中断できるようにしたい場合は、スレッドに定期的にフラグをチェックさせることができます。これを停止するには、フラグを true に設定します。旗を作ることで volatile
, を使用することなく、チェックしているスレッドが次回チェックするときに設定されたことを確認できるようになります。 synchronized
ブロック。
で宣言された変数 volatile
キーワードには、それを特別なものにする 2 つの主な性質があります。
揮発性変数がある場合、どのスレッドによってもコンピュータ (マイクロプロセッサ) のキャッシュ メモリにキャッシュすることはできません。アクセスは常にメインメモリから行われました。
ある場合 書き込み操作 揮発性変数が発生し、突然 読み取り操作 が要求された場合、 書き込み操作は読み取り操作の前に終了します.
上記の 2 つの性質から推測されるのは、
- 揮発性変数を読み取るすべてのスレッドは、必ず最新の値を読み取ります。キャッシュされた値がそれを汚染する可能性がないためです。また、読み取りリクエストは、現在の書き込み操作の完了後にのみ許可されます。
そしてその一方で、
- さらに詳しく調べてみると、 #2 先ほど述べたように、
volatile
キーワードは、次のような共有変数を維持する理想的な方法です。 「n」個の読み取りスレッドと 1 つの書き込みスレッドのみ アクセスするには。を追加したら、volatile
キーワード、完了です。スレッドセーフに関するその他のオーバーヘッドはありません。
逆に、
私たちは できない 利用する volatile
キーワードのみを使用して、次の共有変数を満たすために使用します。 複数の書き込みスレッドがアクセスしている.
はい、可変変数を複数のスレッドでアクセスしたい場合は常に volatile を使用する必要があります。通常、複数のアトミック操作を実行する必要があるため、これはあまり一般的なユースケースではありません (例:変更する前に変数の状態を確認してください)。その場合は、代わりに同期ブロックを使用します。
long および double 変数型の読み取りおよび書き込み操作の処理については誰も言及していません。読み取りと書き込みは、参照変数とほとんどのプリミティブ変数のアトミック操作です。ただし、long 型と double の変数タイプは例外であり、アトミック操作になるには volatile キーワードを使用する必要があります。 @リンク
私の意見では、volatile キーワードが使用されているスレッドを停止する以外の 2 つの重要なシナリオは次のとおりです。
- 二重チェックされたロック機構. 。シングルトンのデザインパターンで頻繁に使用されます。この中で、 シングルトンオブジェクトは揮発性として宣言する必要があります.
- 偽のウェイクアップ. 。通知呼び出しが発行されていない場合でも、スレッドが待機呼び出しからウェイクアップする場合があります。この動作は擬似ウェイクアップと呼ばれます。これは条件変数(ブールフラグ)を使用することで対抗できます。フラグが true である限り、wait() 呼び出しを while ループに入れます。したがって、スレッドがnotify/notifyall以外の何らかの理由で待機呼び出しからウェイクアップした場合、フラグがまだtrueであることに遭遇するため、再び待機呼び出しが行われます。通知を呼び出す前に、このフラグを true に設定します。この場合、 ブール値フラグは揮発性として宣言されています.
マルチスレッド アプリケーションを開発している場合は、「volatile」キーワード、または「synchronized」キーワード、およびその他の同時実行制御ツールやテクニックを自由に使用する必要があります。このようなアプリケーションの例はデスクトップ アプリです。
アプリケーション サーバー (Tomcat、JBoss AS、Glassfish など) にデプロイされるアプリケーションを開発している場合は、アプリケーション サーバーによってすでに処理されているため、同時実行制御を自分で処理する必要はありません。実際、私の記憶が正しければ、Java EE 標準では、サーブレットと EJB での同時実行制御は禁止されています。これは、サーブレットと EJB は処理から解放されるはずの「インフラストラクチャ」層の一部であるためです。シングルトン オブジェクトを実装している場合、そのようなアプリで同時実行制御のみを実行します。Spring のようなフレームワークを使用してコンポーネントを編んでいる場合でも、この問題はすでに解決されています。
したがって、アプリケーションが Web アプリケーションであり、Spring や EJB などの IoC フレームワークを使用する Java 開発のほとんどの場合、「volatile」を使用する必要はありません。
volatile
すべてのスレッド (それ自体を含む) がインクリメントされていることを保証するだけです。例えば:カウンタは変数の同じ面を同時に見ます。これは同期化やアトミックなどの代わりに使用されるのではなく、読み取りを完全に同期化します。他の Java キーワードと比較しないでください。以下の例が示すように、揮発性変数の操作もアトミックであり、一度に失敗または成功します。
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 を使用すると、結果は常に同じになります。これは同期でも同様です。
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);
}
}
}
はい、私はこれをよく使用します。マルチスレッド コードには非常に役立ちます。あなたが指摘した記事は良い記事です。ただし、留意すべき重要な点が 2 つあります。
- それが何をし、どのように異なるかを同期するのと完全に理解している場合にのみ、揮発性を使用する必要があります。多くの状況では、表面上で揮発性が、同期するよりも簡単なパフォーマンスの代替品であるように見えます。多くの場合、揮発性をよりよく理解することが、同期が機能する唯一のオプションであることを明らかにします。
- 揮発性は実際には多くの古いJVMで動作しませんが、同期していますが。さまざまな JVM のさまざまなレベルのサポートについて言及したドキュメントを見た記憶がありますが、残念ながら今は見つかりません。Java 1.5 より前のバージョンを使用している場合、またはプログラムが実行される JVM を制御できない場合は、必ず検討してください。
はいぜったいに。(Java だけでなく C# でもです。) 特定のプラットフォーム上でアトミックな操作であることが保証されている値、たとえば int や boolean を取得または設定する必要がある場合がありますが、必須ではありません。スレッドロックのオーバーヘッド。volatile キーワードを使用すると、値を読み取るときに確実に 現在 値ではなく、別のスレッドへの書き込みによって単に廃止されたキャッシュされた値ではありません。
揮発性フィールドにアクセスするすべてのスレッドは、(潜在的に) キャッシュされた値を使用する代わりに、続行する前に現在の値を読み取ります。
メンバー変数のみが揮発性または一時的であることができます。
volatile キーワードには 2 つの異なる使用法があります。
- JVM がレジスタ (キャッシュとして想定) から値を読み取るのを防ぎ、その値をメモリから強制的に読み取ります。
- メモリ不整合エラーのリスクを軽減します。
JVMがレジスタで値を読み取ることを防ぎ、その値をメモリから読み取ります。
あ 忙しいフラグ デバイスがビジー状態でフラグがロックで保護されていないときにスレッドが続行されないようにするために使用されます。
while (busy) {
/* do something else */
}
別のスレッドがスレッドをオフにしても、テスト スレッドは続行されます。 忙しいフラグ:
busy = 0;
ただし、busy はテスト スレッドで頻繁にアクセスされるため、JVM は、busy の値をレジスタに配置することでテストを最適化し、各テストの前にメモリ内の Busy の値を読み取らずにレジスタの内容をテストすることがあります。テスト スレッドではビジーの変化が表示されず、他のスレッドはメモリ内のビジーの値のみを変更するため、デッドロックが発生します。を宣言する 忙しいフラグ volatile では、各テストの前にその値が強制的に読み取られます。
メモリ整合性エラーのリスクを軽減します。
揮発性変数を使用すると、次のリスクが軽減されます。 メモリ整合性エラー, なぜなら、揮発性変数への書き込みは、「前に起こる」 同じ変数の後続の読み取りとの関係。これは、揮発性変数への変更が他のスレッドから常に見えることを意味します。
メモリ一貫性エラーを発生させずに読み取り、書き込みを行う技術は、と呼ばれます。 原子アクション.
アトミック アクションとは、事実上一度にすべて発生するアクションです。アトミック アクションは途中で停止できません。それは完全に起こるか、まったく起こらないかのどちらかです。アトミック アクションの副作用は、アクションが完了するまでは表示されません。
アトミックに指定できるアクションを以下に示します。
- 読み取りと書き込みは、参照変数およびほとんどのプリミティブ変数(ロングとダブルを除くすべてのタイプ)のアトミックです。
- 宣言されたすべての変数の読み取りと書き込みはアトミックです 揮発性の(long 変数と double 変数を含む)。
乾杯!
揮発性変数は軽量の同期です。すべてのスレッド間の最新データの可視性が要件であり、アトミック性が損なわれる可能性がある場合、そのような状況では、揮発性変数を優先する必要があります。揮発性変数の読み取りは、レジスタや他のプロセッサが認識できないキャッシュにキャッシュされないため、スレッドによって行われた最新の書き込みを常に返します。揮発性はロックフリーです。シナリオが上記の基準を満たしている場合、私は volatile を使用します。
Volatile は次のことを行います。
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
オラクルのドキュメントより ページ, 、メモリの一貫性の問題を解決するには、揮発性変数の必要性が生じます。
揮発性変数を使用すると、揮発性変数への書き込みによって、同じ変数の後続の読み取りとの事前発生関係が確立されるため、メモリ整合性エラーのリスクが軽減されます。
これは、に変化することを意味します volatile
変数は他のスレッドから常に参照可能です。また、スレッドが揮発性変数を読み取るときに、その変数に対する最新の変更だけが参照されるわけではないことも意味します。 volatile
, だけでなく、変更を引き起こしたコードの副作用も伴います。
で説明されているように、 Peter Parker
不在の場合に答える volatile
修飾子を使用すると、各スレッドのスタックに変数の独自のコピーが存在する場合があります。変数を次のようにすることで、 volatile
, 、メモリの一貫性の問題が修正されました。
見て ジェンコフ 理解を深めるためのチュートリアル ページ。
volatile と volatile を使用するユースケースの詳細については、関連する SE の質問を参照してください。
Java における volatile と synchronized の違い
実際の使用例の 1 つ:
多くのスレッドがあり、現在時刻を特定の形式で出力する必要があります。たとえば、次のようになります。 java.text.SimpleDateFormat("HH-mm-ss")
. 。現在時刻を次のように変換するクラスを 1 つ持つことができます。 SimpleDateFormat
1 秒ごとに変数を更新します。他のすべてのスレッドは、この揮発性変数を使用するだけで、現在の時刻をログ ファイルに出力できます。
あ 揮発性変数 Java アプリケーションでスレッドを同時に実行することにより、非同期的に変更されます。現在「メイン」メモリに保持されている値とは異なる変数のローカル コピーを持つことは許可されていません。実際には、volatile として宣言された変数は、そのデータがすべてのスレッド間で同期されている必要があります。これにより、いずれかのスレッドで変数にアクセスまたは更新するたびに、他のすべてのスレッドが即座に同じ値を参照できるようになります。もちろん、スレッドが独自のデータのコピーを持つことができる理由は、効率を高めるためであるため、揮発性変数は「プレーン」変数よりもアクセスと更新のオーバーヘッドが高い可能性があります。
フィールドが volatile と宣言されると、コンパイラとランタイムは、この変数が共有されていること、およびその変数に対する操作が他のメモリ操作と並べ替えられるべきではないことを通知します。Volatile 変数はレジスタやキャッシュにキャッシュされず、他の変数から隠蔽されます。したがって、揮発性変数を読み取ると、常にスレッドによる最新の書き込みが返されます。
参考までに、これを参照してください http://techno-terminal.blogspot.in/2015/11/what-are-volatile-variables.html
volatile キーを変数とともに使用すると、この変数を読み取るスレッドが同じ値を参照できるようになります。複数のスレッドで変数の読み取りと書き込みを行っている場合、変数を volatile にするだけでは不十分で、データが破損します。イメージ スレッドは同じ値を読み取っていますが、それぞれが何らかの変更 (カウンターのインクリメントなど) を行っているため、メモリに書き戻すときにデータの整合性が侵害されます。そのため、変数を同期させる必要があります (さまざまな方法が可能です)。
変更が 1 つのスレッドによって行われ、他のスレッドがこの値を読み取るだけで済む場合は、volatile が適しています。
好き ジェンコフ氏の説明:
ジャワ 揮発性の キーワードは、Java 変数を「メイン メモリに格納されている」とマークするために使用されます。より正確には、揮発性変数の読み取りはすべて CPU キャッシュからではなくコンピューターのメイン メモリから読み取られ、揮発性変数へのすべての書き込みは CPU キャッシュだけでなくメイン メモリに書き込まれることを意味します。 。
実際、Java 5以来、揮発性キーワードは、揮発性変数が書き込まれ、メインメモリから読み取られているだけではありません。
拡張可視性保証、いわゆるハプニングビフォア保証です。
volatile のパフォーマンスに関する考慮事項
揮発性変数の読み取りと書き込みにより、変数はメイン メモリに読み取られるか、メイン メモリに書き込まれます。メイン メモリへの読み書きは、CPU キャッシュにアクセスするよりもコストがかかります。揮発性変数にアクセスすると、通常のパフォーマンス向上手法である命令の並べ替えも妨げられます。したがって、変数の可視性を強制する必要がある場合にのみ、揮発性変数を使用する必要があります。
volatile 変数は基本的に、更新後のメイン共有キャッシュ ラインの即時更新 (フラッシュ) に使用されるため、変更はすべてのワーカー スレッドに即座に反映されます。
以下は、要件を示す非常に簡単なコードです。 volatile
他のスレッドからスレッドの実行を制御するために使用される変数 (これは、 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