回答:
C#とJavaの両方で、 "volatile"は、変数の値がプログラム自体のスコープ外で変更される可能性があるため、変数の値をキャッシュしてはならないことをコンパイラーに指示します。コンパイラーは、変数が「その制御外」に変更された場合に問題を引き起こす可能性のある最適化を回避します。
この例を考えてみましょう:
int i = 5;
System.out.println(i);
コンパイラはこれを最適化して、次のように5を出力するだけです。
System.out.println(5);
ただし、変更可能な別のスレッドがある場合i
、これは間違った動作です。別のスレッドi
が6に変更されても、最適化されたバージョンは5を出力します。
volatile
キーワードは、最適化とキャッシングを防止し、従って、変数が別のスレッドによって変更することができる場合に有用です。
i
マークが付けられていても、最適化は引き続き有効であると思いますvolatile
。Javaでは、それはすべて発生前の関係についてです。
i
ローカル変数で、他のスレッドはとにかくそれを変更することはできません。フィールドの場合、コンパイラは、それ以外の場合、呼び出しを最適化できませんfinal
。私は、フィールドがfinal
明示的に宣言されていない場合にフィールドが「見える」と仮定して、コンパイラーが最適化を行うことができないと思います。
volatileが変数に対して行うことを理解するには、変数がvolatileでない場合に何が起こるかを理解することが重要です。
2つのスレッドAとBが不揮発性変数にアクセスしている場合、各スレッドはそのローカルキャッシュに変数のローカルコピーを保持します。スレッドAがローカルキャッシュで行った変更は、スレッドBからは見えません。
変数が揮発性であると宣言されている場合、それは本質的に、スレッドがそのような変数をキャッシュしてはならない、つまり、メインメモリから直接読み取られない限り、スレッドはこれらの変数の値を信頼してはならないことを意味します。
では、変数を揮発性にするのはいつですか?
多くのスレッドがアクセスできる変数があり、プログラムの他のスレッド/プロセス/外部によって値が更新された場合でも、すべてのスレッドがその変数の最新の更新された値を取得するようにする場合。
揮発性フィールドの読み取りは取得セマンティクスを持っています。つまり、揮発性変数からのメモリ読み取りは、後続のメモリ読み取りの前に行われることが保証されます。コンパイラが並べ替えを実行するのをブロックし、ハードウェアがそれを必要とする場合(弱い順序のCPU)、揮発性読み取りの後に発生したが投機的に早期に開始された読み取りをハードウェアがフラッシュするように特別な命令を使用します。ロード獲得の問題とそのリタイアの間に投機的ロードが発生するのを防ぐことにより、それらが最初に発行されるのを防ぎます。
volatileフィールドの書き込みには、リリースのセマンティクスがあります。つまり、揮発性変数へのメモリの書き込みは、以前のメモリへのすべての書き込みが他のプロセッサから見えるようになるまで遅延されることが保証されます。
次の例について考えてみます。
something.foo = new Thing();
場合はfoo
、クラスのメンバ変数であり、他のCPUがによって参照されるオブジェクトのインスタンスへのアクセスを持ってsomething
、彼らは、値表示される場合がありますfoo
変更をする前に、メモリへの書き込みThing
コンストラクタがグローバルに見えます!これが「弱い順序のメモリ」の意味です。これは、コンパイラがストアの前のコンストラクタにすべてのストアを持っている場合でも発生する可能性がありますfoo
。場合は、foo
あるvolatile
その後する店舗foo
のリリースのセマンティクスを持つことになり、ハードウェア保証は、書き込み前に書き込みのすべてのことにfoo
書き込みができるようにする前に、他のプロセッサに見えるfoo
が発生します。
書き込みがfoo
非常にひどく並べ替えられる可能性があるのはなぜですか?キャッシュラインの保持foo
がキャッシュ内にあり、コンストラクター内のストアがキャッシュをミスした場合、ストアはキャッシュへの書き込みがミスするよりもはるかに早く完了する可能性があります。
Intelの(ひどい)Itaniumアーキテクチャは、メモリの順序付けが弱かった。元のXBox 360で使用されていたプロセッサは、メモリの順序が弱かったです。非常に人気の高いARMv7-Aを含む多くのARMプロセッサは、メモリの順序が弱くなっています。
ロックのようなものは完全なメモリバリアを実行するため、開発者はこれらのデータレースを見ないことがよくあります。これは、本質的に同時にセマンティクスを取得および解放することと同じです。ロック内のロードは、ロックを取得する前に投機的に実行することはできません。ロックが取得されるまで遅延されます。ロックの解放全体でストアを遅延させることはできません。ロックを解放する命令は、ロック内で行われたすべての書き込みがグローバルに見えるようになるまで遅延されます。
より完全な例は、「ダブルチェックロック」パターンです。このパターンの目的は、オブジェクトを遅延初期化するために常にロックを取得する必要がないようにすることです。
ウィキペディアに引っかかった:
public class MySingleton {
private static object myLock = new object();
private static volatile MySingleton mySingleton = null;
private MySingleton() {
}
public static MySingleton GetInstance() {
if (mySingleton == null) { // 1st check
lock (myLock) {
if (mySingleton == null) { // 2nd (double) check
mySingleton = new MySingleton();
// Write-release semantics are implicitly handled by marking
// mySingleton with 'volatile', which inserts the necessary memory
// barriers between the constructor call and the write to mySingleton.
// The barriers created by the lock are not sufficient because
// the object is made visible before the lock is released.
}
}
}
// The barriers created by the lock are not sufficient because not all threads
// will acquire the lock. A fence for read-acquire semantics is needed between
// the test of mySingleton (above) and the use of its contents. This fence
// is automatically inserted because mySingleton is marked as 'volatile'.
return mySingleton;
}
}
この例では、MySingleton
コンストラクター内のストアは、ストアtoの前に他のプロセッサーから見えない可能性がありますmySingleton
。その場合、mySingletonを覗く他のスレッドはロックを取得せず、コンストラクターへの書き込みを必ずしも取得しません。
volatile
キャッシュを防ぐことはありません。それが行うことは、他のプロセッサが「見る」書き込みの順序を保証することです。ストアのリリースは、保留中のすべての書き込みが完了するまでストアを遅らせ、バスサイクルが発行されて、他のプロセッサに関連するラインがキャッシュされた場合にキャッシュラインを破棄/ライトバックするように指示します。負荷取得は、推測された読み取りをフラッシュし、それらが過去の古い値にならないようにします。
head
とtail
仮定してからプロデューサーを防ぐために揮発性である必要性tail
は変わりません、と仮定してから消費者を防ぐためにhead
変更されません。また、head
ストアがグローバルに表示される前に、キューデータの書き込みがグローバルに表示されるようにするには、揮発性でなければなりませんhead
。
volatileキーワードは、JavaとC#の両方で異なる意味を持っています。
フィールドは揮発性として宣言できます。その場合、Javaメモリモデルは、すべてのスレッドが変数の一貫した値を参照することを保証します。
volatileキーワードのC#リファレンスから:
volatileキーワードは、オペレーティングシステム、ハードウェア、または同時に実行されているスレッドなどによって、プログラム内のフィールドを変更できることを示します。
Javaでは、 "volatile"を使用して、変数が複数のスレッドで同時に使用される可能性があることをJVMに通知するため、特定の一般的な最適化を適用できません。
特に、同じ変数にアクセスする2つのスレッドが同じマシンの別々のCPUで実行されている状況。メモリアクセスはキャッシュアクセスよりも非常に遅いため、CPUが保持するデータを積極的にキャッシュすることは非常に一般的です。これは、データがCPU1で更新された場合、キャッシュがそれ自体をクリアすることを決定するときではなく、すべてのキャッシュを通過してメインメモリに移動する必要があるため、CPU2は更新された値を(再びすべてのキャッシュを無視して)確認できることを意味します。
不揮発性のデータを読み取る場合、実行中のスレッドは常に更新された値を取得する場合と取得しない場合があります。ただし、オブジェクトが揮発性の場合、スレッドは常に最新の値を取得します。