定时循环不终止

时间:2015-05-27 05:19:38

标签: java timer while-loop timertask

运行此代码,我希望它将测试变量增加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++;
    }

程序在预期的时间内完成(并打印出很多行)。我不明白。为什么会出现这种情况?

3 个答案:

答案 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,它详细描述了这个问题。