我写下了这段代码:
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
变量,如果为true,为什么在运行时不以某种方式定期刷新此变量?
很显然,volatile
关键字解决了该问题,但是它没有回答我这个问题,因为volatile
可以确保可见性并防止JIT达到最佳化的数量。
更重要的是,当我将sleep
时间更改为1ms时,第二个线程将正确终止,因此我很确定这与变量可见性无关。
UPDATE:值得一提的是,当counter
时间设置为1ms时,我从sleep
中获得了非零值。
UPDATE 2:另外,我可以说-XX:+PrintCompilation
表示如果将sleep
时间设置为100 ms,则会编译while
循环,并且会发生On Stack Replacement
。
UPDATE 3:可能这就是我想要的:https://www.youtube.com/watch?v=ADxUsCkWdbE&feature=youtu.be&t=889。而且我认为-这是JIT进行的“优化”之一,防止这种情况的方法是将变量指定为volatile
。
原因不是JIT优化。您的代码正在对共享变量stopThread
进行非同步访问(我们称其为“标志”)。
基本上存在竞赛条件”。如果在进入循环之前将该标志设置为true,则代码完成。如果没有(种族丢失),则循环将不确定地持续很长时间,因为CPU高速缓存保留了falsey值。当标志为volatile
时,将从主存储器而不是CPU缓存中读取其值,并且该标志最终在标志设置线程完成休眠后立即结束循环。
我认为您是从错误的方向解决此问题的。
您拥有的程序在内存可见性方面的行为未指定。如果两个线程正在读写共享变量,则必须以特定方式完成此操作,以确保一个线程能看到其他线程的写入。您需要使用synchronized
或volatile
变量来确保写入发生在前面读取。
如果<< [happens before不存在,则允许JVM假定只有当前线程正在访问/更新变量。 JIT编译器实际执行的操作取决于各种各样的事情,而进入细节通常没有帮助。
还值得注意的是,volatile
不一定是针对内存可见性缺陷的最佳(最有效)解决方案它到底做了什么以及为什么它与sleep(1)一起工作,我们只能推测。我的猜测是,在第二个线程读取它之前,第一个线程将stopThread = true写入。也许尝试先启动循环线程。