Mar*_*tin 2 java performance jit jvm
我写下了这段代码:
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");
}
}
Run Code Online (Sandbox Code Playgroud)
当我运行它时,while循环将永远运行。我对此有点挣扎,我对应用于此代码的优化 JIT 类型感到困惑。
首先我认为这是具有知名度的问题stopThread变量,但即使它是一个真正的while循环应该有点晚停比我分配stopThread到true时(从第1线程CPU缓存得到刷新到主存储器),所以事实并非如此。看起来像 JIT 硬编码false到stopThread变量,如果它是真的,为什么这个变量在运行时不会以某种方式定期刷新?
显然,volatile关键字解决了这个问题,但它并没有回答我这个问题,因为volatile可以确保可见性并防止 JIT 进行多次优化。
更重要的是,当我将sleep时间更改为 1ms 时,第二个线程将正确终止,因此我很确定这与可变可见性无关。
更新:值得一提的是,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 所说:
这种优化是循环不变提升。JIT 允许将 stopThread 的负载移出循环,因为它可能假设 non-volatile 字段不会在外部发生变化,而且 JIT 还看到 stopThread 在循环内部不会发生变化。
原因不是 JIT 优化。您的代码正在对共享变量进行非同步访问stopThread(我们称之为“标志”)。
本质上,在将标志设置为真值的线程与正在检查该值的另一个线程之间存在竞争条件。如果在进入循环之前将该标志设置为真,则代码完成。如果不是(比赛失败),循环将不确定地持续多久,因为 CPU 缓存保存了 falsey 值。当标志为 时volatile,它的值是从主内存而不是 CPU 缓存中读取的,并且循环最终会在标志设置线程完成睡眠后立即结束。