問題は、コードが最適化されて値がキャッシュされるまで十分に待機していないことです。
x86_64 システムのスレッドが初めて値を読み取るとき、スレッドセーフなコピーを取得します。その後の変更のみが表示されない可能性があります。これは、他の CPU では当てはまらない場合があります。
これを試すと、各スレッドがそのローカル値でスタックしていることがわかります。
public class RequiresVolatileMain {
static volatile boolean value;
public static void main(String... args) {
new Thread(new MyRunnable(true), "Sets true").start();
new Thread(new MyRunnable(false), "Sets false").start();
}
private static class MyRunnable implements Runnable {
private final boolean target;
private MyRunnable(boolean target) {
this.target = target;
}
@Override
public void run() {
int count = 0;
boolean logged = false;
while (true) {
if (value != target) {
value = target;
count = 0;
if (!logged)
System.out.println(Thread.currentThread().getName() + ": reset value=" + value);
} else if (++count % 1000000000 == 0) {
System.out.println(Thread.currentThread().getName() + ": value=" + value + " target=" + target);
logged = true;
}
}
}
}
}
値の反転を示す次のように出力しますが、スタックします。
Sets true: reset value=true
Sets false: reset value=false
...
Sets true: reset value=true
Sets false: reset value=false
Sets true: value=false target=true
Sets false: value=true target=false
....
Sets true: value=false target=true
Sets false: value=true target=false
このスイッチを追加すると-XX:+PrintCompilation
、あなたが見る頃に起こります
1705 1 % RequiresVolatileMain$MyRunnable::run @ -2 (129 bytes) made not entrant
1705 2 % RequiresVolatileMain$MyRunnable::run @ 4 (129 bytes)
コードがネイティブにコンパイルされたことを示唆するものは、スレッドセーフではない方法です。
値を作成すると、volatile
値が際限なく(または飽きるまで)反転することがわかります
編集:このテストが行うことは次のとおりです。値がスレッドのターゲット値ではないことを検出すると、値を設定します。すなわち。スレッド 0 が に設定されtrue
、スレッド 1 が に設定されますfalse
。2 つのスレッドがフィールドを適切に共有している場合、互いの変更が確認され、値が常に true と false の間で反転します。
volatile がないと、これは失敗し、各スレッドは独自の値のみを参照するため、値を変更し、スレッド 0true
とスレッド 1の両方false
が同じフィールドを参照します。