ウィキから:
特に Java では、先行発生関係は、ステートメント A によって書き込まれたメモリがステートメント B から見えること、つまり、ステートメント B が読み取りを開始する前にステートメント A が書き込みを完了することを保証するものです。
そのため、スレッド A が値 10 で ta を書き込み、スレッド B がその後に ta を読み取ろうとすると、先行発生関係により、スレッド B はスレッド A によって書き込まれた値 10 を読み取らなければならず、他の値は読み取れないことが保証されます。アリスが牛乳を買って冷蔵庫に入れ、ボブが冷蔵庫を開けて牛乳を見るのと同じように、それは自然なことです. ただし、コンピュータが実行されている場合、通常、メモリ アクセスは直接メモリにアクセスすることはなく、遅すぎます。代わりに、ソフトウェアはレジスターまたはキャッシュからデータを取得して時間を節約します。キャッシュミスが発生した場合にのみメモリからデータをロードします。問題が発生すること。
質問のコードを見てみましょう。
class Test {
volatile int a;
public static void main(String ... args) {
final Test t = new Test();
new Thread(new Runnable(){ //thread A
@Override
public void run() {
Thread.sleep(3000);
t.a = 10;
}
}).start();
new Thread(new Runnable(){ //thread B
@Override
public void run() {
System.out.println("Value " + t.a);
}
}).start();
}
}
スレッド A は値 ta に 10 を書き込み、スレッド B はそれを読み取ろうとします。スレッド A がスレッド B を読み取る前に書き込むとします。スレッド B が読み取ると、レジスタまたはキャッシュに値をキャッシュしないため、メモリから値がロードされ、スレッド A によって常に 10 が書き込まれるとします。スレッド B が読み取り、スレッド B が初期値 (0) を読み取ります。したがって、この例では、volatile の動作とその違いは示されていません。しかし、次のようにコードを変更すると:
class Test {
volatile int a;
public static void main(String ... args) {
final Test t = new Test();
new Thread(new Runnable(){ //thread A
@Override
public void run() {
Thread.sleep(3000);
t.a = 10;
}
}).start();
new Thread(new Runnable(){ //thread B
@Override
public void run() {
while (1) {
System.out.println("Value " + t.a);
}
}
}).start();
}
}
volatileを使用しない場合、スレッド A が ta に 10 を書き込んだ後に何らかの読み取りが発生したとしても、出力値は常に初期値 (0) である必要があり、これは発生前の関係に違反します。その理由は、コンパイラがコードを最適化し、ta をレジスタに保存し、キャッシュ メモリから読み取る代わりにレジスタ値を使用するたびに、もちろんはるかに高速になるためです。しかし、他のスレッドが更新した後にスレッド B が正しい値を取得できないため、先行発生関係違反の問題も発生します。
上記の例では、volatile 書き込みが volatile 読み取りの前に発生するということは、スレッド A が更新した後にvolatileスレッド B が ta の正しい値を取得することを意味します。コンパイラは、スレッド B が ta を読み取るたびに、レジスタの古い値を使用するだけでなく、キャッシュまたはメモリから読み取る必要があることを保証します。