【发布时间】:2020-04-13 20:27:58
【问题描述】:
我写下了这段代码:
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 稍晚一点(当来自第一个线程的 CPU 缓存被刷新到主内存时),所以情况并非如此。它看起来像 JIT 硬编码 false 到 stopThread 变量,如果是真的,为什么这个变量没有在运行时以某种方式定期刷新?
显然,volatile 关键字解决了这个问题,但它并没有回答我这个问题,因为volatile 可以确保可见性并防止 JIT 进行优化次数。
更重要的是,当我将 sleep 时间更改为 1 毫秒时,第二个线程将正确终止,所以我很确定这与变量可见性无关。
更新:值得一提的是,当 sleep 时间设置为 1-10 毫秒时,我从 counter 获得了非零值。
更新 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 所说:
这种优化是循环不变提升。允许 JIT 将 stopThread 的负载移出循环,因为它可能假设非易失性字段在外部没有变化,并且 JIT 还看到 stopThread 在循环内部没有变化。
【问题讨论】:
-
这个优化是Loop invariant hoisting。允许 JIT 将
stopThread的负载移出循环,因为它可能假设非易失性字段不会在外部发生变化,并且 JIT 还可以看到stopThread在循环内没有变化。 -
@apangin 可能这就是为什么
loadLoadFance()也可以工作,而无需将变量指定为volatile。也许仍然需要volatile来防止 CPU 缓存中的值过时。谢谢。 -
@apangin,您可以将其添加为答案,因为当前答案未提及此处发生的确切优化。
标签: java performance jvm jit