运行此代码,我希望它将测试变量增加5秒然后完成。
import java.util.Timer;
import java.util.TimerTask;
public class Test {
private static boolean running;
public static void main( String[] args ) {
long time = 5 * 1000; // converts time to milliseconds
long test = Long.MIN_VALUE;
running = true;
// Uses an anonymous class to set the running variable to false
Timer timer = new Timer();
timer.schedule( new TimerTask() {
@Override
public void run() { running = false; }
}, time );
while( running ) {
test++;
}
timer.cancel();
System.out.println( test );
}
}
然而,当我运行它时程序没有结束(我假设,我给了它一段合理的时间)。但是,如果我将while循环更改为
while( running ) {
System.out.println();
test++;
}
程序在预期的时间内完成(并打印出很多行)。我不明白。为什么会出现这种情况?
答案 0 :(得分:4)
根据Java Memory Model,无法保证何时从另一个线程可以看到非易失性字段。在您的情况下,您的main方法是JIT编译的,JIT编译器合理地假设由于当前线程不修改循环中的running
变量,那么我们不应该在每次迭代时都阅读它并将循环转换为无限循环。对于这种情况,甚至有FindBugs warning。
当您添加System.out.println
调用时,似乎JIT编译器无法内联它,因此无法确定此方法不会修改running
变量,因此它会关闭字段读取优化。但是,这不应被视为问题解决方案:即使您内部有System.out.println
,新版本的Java也可能更智能并优化您的循环。
答案 1 :(得分:2)
让我们更接近您的代码...
如果您调试代码,它将起作用。那是因为你只会看到一个线程而没有缓存。 Java可以使用基于线程的缓存机制。您需要读取并将其写入主存储器。
因此,如果在运行变量上使用volatile
关键字,jvm会将其视为多个线程可编辑,并且不应缓存它。
因此,在您的情况下,解决方案是将volatile
添加到正在运行的变量中。
答案 2 :(得分:2)
您正在使用running
方法更新其他主题中的main
变量。 Java内存模型使您无法保证在不同的线程中看到非易失性变量的更新。
最简单的解决方案是创建变量volatile
:这会强制在线程访问变量时检查变量的“当前”值。
其他解决方案包括使用AtomicBoolean
代替boolean
,并在running
块中包含对synchronized
的访问权限(即访问running
的代码已同步在与更新它的代码相同的监视器上。)
我强烈建议您选择一份Java Concurrency In Practice,它详细描述了这个问题。