ブログや記事などをもっと読んだ後、メモリバリアの前後のロード/ストアの動作について本当に混乱しています。
以下は、JMMに関する彼の説明記事の1つにあるDoug Leaからの2つの引用であり、どちらも非常に単純です。
しかし、メモリバリアについて別の blog を調べたところ、次のようになりました。
私にとって、Doug Leaの説明は他の説明よりも厳密です。基本的に、ロードバリアとストアバリアが異なるモニター上にある場合、データの一貫性は保証されません。ただし、後者は、バリアが異なるモニター上にある場合でも、データの一貫性が保証されることを意味します。これら2つを正しく理解しているかどうかはわかりません。また、どちらが正しいかわかりません。
次のコードを検討します。
public class MemoryBarrier {
volatile int i = 1, j = 2;
int x;
public void write() {
x = 14; //W01
i = 3; //W02
}
public void read1() {
if (i == 3) { //R11
if (x == 14) //R12
System.out.println("Foo");
else
System.out.println("Bar");
}
}
public void read2() {
if (j == 2) { //R21
if (x == 14) //R22
System.out.println("Foo");
else
System.out.println("Bar");
}
}
}
1つの書き込みスレッドTW1が最初にMemoryBarrierのwrite()メソッドを呼び出し、次に2つのリーダースレッドTR1とTR2がMemoryBarrierのread1()とread2()メソッドを呼び出すとしましょう。このプログラムが順序を保持しないCPUで実行されていると考えてください(x86そのような場合の順序を保持してください)、メモリモデルによると、W01/W02の間にStoreStoreバリア(たとえばSB1)があり、R11/R12とR21/R22の間に2つのLoadLoadバリアがあります( RB1とRB2と言います)。
どちらが正しいか、または両方が正しいかはわかりませんが、MartinThompsonが説明したのはx86アーキテクチャ専用です。 JMMは、xへの変更がTR2に表示されることを保証しませんが、x86の実装は表示します。
ありがとう〜
ダグ・リーは正しいです。関連する部分は、Java言語仕様のセクション §17.4.4 にあります。
[..]揮発性変数への書き込みv(§8.3.1.4)同期-with任意のスレッドによるvの後続のすべての読み取り(ここで、「後続」は同期順序に従って定義されます)。 [..]
concrete machineのメモリモデルは重要ではありません。これは、Javaプログラミング言語のセマンティクスがabstract machineで定義されているためです。 = コンクリートマシンから独立。Javaランタイム環境は、次の保証に準拠するようにコードを実行する責任があります。 Java言語仕様。
実際の質問について:
read2
印刷可能"Bar"
、なぜならread2
はwrite
の前に実行できます。CountDownLatch
との追加の同期がある場合は、read2
が実行されますafterwrite
、次にメソッドread2
は決して印刷しません"Bar"
、CountDownLatch
との同期により、x
の-データ競合が削除されるためです。独立した揮発性変数:
揮発性変数への書き込みが他の揮発性変数の読み取りと同期しないことは理にかなっていますか?
はい、それは理にかなっています。 2つのスレッドが相互作用する必要がある場合、通常、情報を交換するために同じvolatile
変数を使用する必要があります。一方、スレッドが他のすべてのスレッドと対話する必要なしに揮発性変数を使用する場合、メモリバリアのコストを支払う必要はありません。
それは実際には重要です。例を挙げましょう。次のクラスは、揮発性メンバー変数を使用します。
class Int {
public volatile int value;
public Int(int value) { this.value = value; }
}
このクラスがメソッド内でローカルにのみ使用されると想像してください。 JITコンパイラは、オブジェクトがこのメソッド内でのみ使用されていることを簡単に検出できます( エスケープ分析 )。
public int deepThought() {
return new Int(42).value;
}
上記のルールを使用すると、volatile
変数は他のスレッドからアクセスできないため、JITコンパイラはvolatile
の読み取りと書き込みのすべての影響を取り除くことができます。
この最適化は、実際にはJava JITコンパイラ:
私が理解している限り、質問は実際には揮発性の読み取り/書き込みとその発生-保証前です。その部分について言えば、nosidの答えに追加することが1つだけあります。
揮発性の書き込みは通常の書き込みの前に移動できません。揮発性の読み取りは通常の読み取りの後に移動できません。そのため、read1()
およびread2()
の結果はnosidが記述したとおりになります。
障壁について言えば、定義は私には問題ないように聞こえますが、おそらくあなたを混乱させる1つのことは、これらがホットスポットでJMMで説明されている動作を実装するためのもの/ツール/方法/メカニズム(好きなように呼んでください)であるということです。 Javaを使用する場合は、実装の詳細ではなく、JMMの保証に依存する必要があります。