定时 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 并发实践,其中详细描述了这个问题。