Проблема в том, что вы недостаточно долго ожидаете оптимизации кода и кэширования значения.
Когда поток в системе x86_64 впервые читает значение, он получает потокобезопасную копию. Его только более поздние изменения он может не увидеть. Это может быть не так на других процессорах.
Если вы попробуете это, вы увидите, что каждый поток застрял со своим локальным значением.
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)
То, что код был скомпилирован в native, - это способ, который не является поточно-ориентированным.
если вы сделаете значение volatile
, вы увидите, что оно бесконечно переворачивает значение (или пока мне не надоест)
РЕДАКТИРОВАТЬ: Что делает этот тест; когда это обнаруживает, что значение не является тем целевым значением потоков, это устанавливает значение. то есть. поток 0 устанавливается на true
, а поток 1 устанавливается на false
Когда два потока правильно разделяют поле, они видят изменения друг друга, и значение постоянно меняется между истиной и ложью.
Без volatile это завершается неудачно, и каждый поток видит только свое собственное значение, поэтому они оба изменяют значение и поток 0, см. true
, а поток 1 видит false
для одного и того же поля.