Я записал этот код:
public class Main {
private boolean stopThread = false;
private int counter = 0;
public static void main(String[] args) throws InterruptedException {
final Main main = new Main();
new Thread(() -> {
try {
System.out.println("Start");
Thread.sleep(100);
System.out.println("Done");
main.stopThread = true;
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
while (!main.stopThread) {
main.counter++;
}
System.out.println(main.counter);
}).start();
System.out.println("End");
}
}
, и когда я его запусту, цикл while
будет работать вечно. Я немного боролся с этим, и я запутался, какой тип оптимизации JIT применяется к этому коду.
Сначала я подумал, что это проблема с видимостью переменной stopThread
, но даже если онаэто правда, цикл while
должен остановиться немного позже, чем я назначил stopThread
на true
(когда кэш процессора из 1-го потока был сброшен в основную память), поэтому это не может быть так. Похоже, что переменная JIT жестко закодирована от false
до stopThread
, и если это правда, почему эта переменная не обновляется периодически как-то во время выполнения?
Очевидно, что ключевое слово volatile
исправило это, но оно не отвечает на этот вопрос, потому что volatile
может обеспечить видимость, а также предотвратить JIT от ряда оптимизаций.
Более того, когда я изменяю время sleep
на 1 мс, второй поток завершается правильно, поэтому я почти уверен, что речь не идет о переменной видимости.
ОБНОВЛЕНИЕ : Стоит отметить, что я получаю ненулевое значение из counter
, когда время sleep
установлено в 1-10 мс.
ОБНОВЛЕНИЕ 2 : Кроме того, я могу сказать, что -XX:+PrintCompilation
показывает, что в случае, если время sleep
установлено равным 100 мс, цикл while
компилируется, а On Stack Replacement
происходит.
ОБНОВЛЕНИЕ 3 : Вероятно, это то, что я искал: https://www.youtube.com/watch?v=ADxUsCkWdbE&feature=youtu.be&t=889. И, как я думал, это одна из «оптимизаций», выполняемых JIT, и способ предотвратить это - указать переменную как volatile
, или введите loadloadFence
в качестве первой строки в цикле while
.
ОТВЕТ : Как сказал @apangin:
Эта оптимизация является Loopинвариантный подъемJIT разрешено перемещать нагрузку stopThread из цикла, поскольку он может предполагать, что энергонезависимое поле не изменяется внешне, и JIT также видит, что stopThread не изменяется внутри цикла.