定时 while 循环不终止
Timed while loop not terminating
运行 这段代码,我希望它会增加测试变量 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++;
}
程序在预期的时间内完成(并打印出很多行)。我不明白。为什么会出现这种行为?
根据Java Memory Model there's no guarantee when non-volatile field will be visible from another thread. In your case your main method is JIT compiled and JIT-compiler reasonably assume that as current thread does not modify the running
variable in a loop, then we should not bother reading it on every iteration and convert the loop to the infinite one. There's even FindBugs warning关于这个案例。
当你添加一个System.out.println
调用时,似乎JIT编译器不能内联它,所以不能确定这个方法没有修改running
变量,因此它关闭了字段读取优化。然而,这不应被视为问题解决方案:即使您在内部有 System.out.println
,新版本的 Java 也可能会更智能并优化您的循环。
让我们更深入地了解您的代码...
如果你调试你的代码,它就会工作。那是因为您只会看到一个线程而没有缓存。 Java 可以使用基于线程的缓存机制。您需要将其读取并写入主内存。
因此,如果您在 运行 变量上使用 volatile
关键字,jvm 会将其视为可由多个线程编辑,并且不应缓存它。
因此在您的情况下,解决方案是将 volatile
添加到您的 运行 变量。
您正在与 main
方法不同的线程中更新 running
变量。 Java 内存模型无法保证您在不同线程中看到非易失性变量的更新。
最简单的解决方案是使变量 volatile
:这会强制在线程访问变量时检查变量的 'current' 值。
其他解决方案包括使用 AtomicBoolean
而不是 boolean
,并将对 running
的访问包装在相互 synchronized
块中(即访问 running
的代码与更新它的代码在同一个监视器上同步。
我强烈建议您阅读 Java 并发实践,其中详细描述了这个问题。
运行 这段代码,我希望它会增加测试变量 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++;
}
程序在预期的时间内完成(并打印出很多行)。我不明白。为什么会出现这种行为?
根据Java Memory Model there's no guarantee when non-volatile field will be visible from another thread. In your case your main method is JIT compiled and JIT-compiler reasonably assume that as current thread does not modify the running
variable in a loop, then we should not bother reading it on every iteration and convert the loop to the infinite one. There's even FindBugs warning关于这个案例。
当你添加一个System.out.println
调用时,似乎JIT编译器不能内联它,所以不能确定这个方法没有修改running
变量,因此它关闭了字段读取优化。然而,这不应被视为问题解决方案:即使您在内部有 System.out.println
,新版本的 Java 也可能会更智能并优化您的循环。
让我们更深入地了解您的代码...
如果你调试你的代码,它就会工作。那是因为您只会看到一个线程而没有缓存。 Java 可以使用基于线程的缓存机制。您需要将其读取并写入主内存。
因此,如果您在 运行 变量上使用 volatile
关键字,jvm 会将其视为可由多个线程编辑,并且不应缓存它。
因此在您的情况下,解决方案是将 volatile
添加到您的 运行 变量。
您正在与 main
方法不同的线程中更新 running
变量。 Java 内存模型无法保证您在不同线程中看到非易失性变量的更新。
最简单的解决方案是使变量 volatile
:这会强制在线程访问变量时检查变量的 'current' 值。
其他解决方案包括使用 AtomicBoolean
而不是 boolean
,并将对 running
的访问包装在相互 synchronized
块中(即访问 running
的代码与更新它的代码在同一个监视器上同步。
我强烈建议您阅读 Java 并发实践,其中详细描述了这个问题。