volatile キーワードは、言語とそれが実装されているプラットフォームに対して非常に主観的です。Java はすべてのアーキテクチャで volatile の一貫した動作を提供しますが、これは、C/C++ の場合のように、ネイティブ マシン プラットフォームに直接コンパイルされる言語には当てはまりません。なぜそうなのかを理解しようとしましょう。
a、b を一連のプログラム アクション P のメンバーとし、v_{n} (a) をアクションにボラティリティ要件を適用する関数とします。ここで、添え字 _n は揮発性アクションが適用される _n 回目の繰り返しを示します。 、および \rightarrow は前に説明した先行演算子です。すべてのプログラム アクションについて、次のルールが適用されます。
v_n(a) \rightarrow v_{n+1}(a)
a \rightarrow v_n(b) \Rightarrow a \rightarrow v_{n+i}(b) ここで、i \in \mathbb{N}
ルール 1 は、すべての揮発性関数が全体的な順序を強制することを示しています。ここで、関数 v_{n} (a) は常に v_{n+1} (a) に先行します。ルール 2 は、アクション a がアクションの揮発性関数に先行する場合、 _n 回目の反復で b を実行する場合、アクション a は、b に適用される後続のすべての揮発性関数より必ず先行する必要があります。
これは Java では非常に強力なメモリ要件であり、実際には C/C++ と比較してはるかに強力です。C/C++ 言語仕様には、メモリの順序付けに関するこのような制限はなく、揮発性アクションの周囲で不揮発性アクションをどのように順序付けるかは、コンパイラの実装に委ねられています。
簡単なコード サンプルを使用して、これらの規則がプログラムの実行にどのように影響するかを考えてみましょう。
int a = 0;
int b = 0;
volatile int count = 0;
a = 1;
count = 1;
b = 2;
count = 2;
C/C++ では、volatile キーワードは、count 変数が相互に並べ替えられないことのみを保証します。count == 2 の場合、必ず count = 1 が先行する必要があります。ただし、a == 1 であるという保証も、b == 2 であるという保証もありません。
Java では、上記で定義されたより強力な保証が与えられた場合、count == 1 の場合、アサーション a == 1 は真でなければなりません。同様に、count == 2 の場合、a == 1 && b == 2 が真でなければならないというアサーション。これは、C/C++ が提供しない Java の厳密なメモリ保証が意味するものです。
ただし、これは、C/C++ が Java と同じように動作しないという意味ではありません。そうするかどうかは、(1) コンパイラが、驚くべき順序ではあるが正当な順序でコードの並べ替えを実行するかどうか、および (2) 基礎となるマシン アーキテクチャが同じ厳密なメモリ順序を維持するかどうかに依存します。コンパイラは、驚くべきコードの並べ替えを実行しません。
たとえば、すべての x86 プラットフォームで -O0 を設定して gcc でコードをコンパイルすると、Java のメモリ モデルに準拠します (より厳密になります)。プログラマーが予期しない動作。警告レクター!
いずれにせよ、メモリ モデルの整合性規則の詳細に興味がある場合は、x86 メモリ モデルに関する Intel の説明をご覧になることをお勧めします。ここでは、メモリの順序付けのニュアンスが詳細に説明されています。楽しみ!