计时的while循环无法终止

5
运行此代码,我期望它会在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++;
    }

程序在预期的时间内完成(并输出了大量的行)。我不明白,为什么会发生这种行为?


如果你将运行设置为 volatile 会怎样? - Andy Turner
我刚刚使用JDK 7和IntelliJ 11测试了你的代码,两个版本都在几秒钟内完成了。你用什么来运行? - Tim Biegeleisen
@AndyTurner 哇,我以前从来没有遇到过 volatile 关键字。这让代码正常工作了。谢谢! - DenverCoder9
@TimBiegeleisen Java 8 更新45 - DenverCoder9
3个回答

4
根据Java内存模型,非volatile字段何时对另一个线程可见没有保证。在您的情况下,您的主方法被JIT编译器编译,并且JIT编译器合理地假设由于当前线程未在循环中修改running变量,因此我们不必在每次迭代中读取它并将循环转换为无限循环。 这种情况甚至会出现FindBugs警告
当您添加System.out.println调用时,似乎JIT编译器无法将其内联,因此它无法确定此方法是否修改了running变量,因此关闭了字段读取优化。但是,这不应视为问题的解决方案:即使您在其中添加了System.out.println,较新版本的Java可能会更加智能化,并优化您的循环。

我接受了这个答案,因为它链接了额外的信息。谢谢! - DenverCoder9

2
让我们更深入地了解您的代码...
如果您调试代码,它将正常工作。这是因为您只会看到一个线程和没有缓存。Java可以使用基于线程的缓存机制。您需要将其读取并写入主内存。
因此,如果您在运行变量上使用volatile关键字,jvm将视其为可由多个线程编辑,并且不应被缓存。
因此,在您的情况下,解决方案是向您的运行变量添加volatile

2
你正在不同的线程中更新running变量,而你的main方法在另一个线程中。Java内存模型规定,在不同的线程中看到非易失性变量的更新结果是不被保证的。
最简单的解决方案是将变量volatile:这会在线程访问它时强制检查变量的“当前”值。
其他解决方案包括使用AtomicBoolean代替boolean,并将对running的访问封装在相互同步的块中(即访问running的代码与更新它的代码在相同的监视器上同步)。
我强烈建议您阅读《Java并发编程实践》,其中详细描述了此问题。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接