为什么Java Future.get(timeout)不可靠?

时间:2010-12-03 23:46:31

标签: java multithreading timeout java.util.concurrent

Future.get(timeout)在给定的超时后不能可靠地抛出TimeoutException。这是正常的行为,还是我可以做些什么来使这更可靠?我的机器上的测试失败了。但是如果我睡了3000而不是2000,它就会过去。

public class FutureTimeoutTest {
@Test
public void test() throws
    ExecutionException,
    InterruptedException {

    ExecutorService exec = Executors.newSingleThreadExecutor();
    final Callable call = new Callable() {
        @Override
        public Object call() throws Exception {
             try {
                Thread.sleep(2000);
            } catch (InterruptedException ex) {
                ex.printStackTrace();
            }
            return 0;
        }
    };
    final Future future = exec.submit(call);
    try {
        future.get(1000, TimeUnit.MILLISECONDS);
        fail("expected TimeoutException");
    } catch (TimeoutException ignore) {
    }
}

}

3 个答案:

答案 0 :(得分:15)

没有理由期望测试通过。鉴于您提交执行任务然后等待其完成,在等待Future#get()开始之前任何时间都可以通过,允许任务有足够的时间耗尽睡眠持续时间并完成。

在您的情况下,我们可以假设在Executor内运行的线程获得焦点,而运行test()的主线程处于保持状态,尽管处于可运行状态。至于在提交的任务停止两到三秒之间观察到的区别,我希望你能找到甚至三秒钟不足的情况,这取决于你的计算机上正忙着做什么。

答案 1 :(得分:11)

@seh是对的。

您期望从Java中通常称为“实时”行为。除非您在实时操作系统上运行的具有实时功能的Java发行版中使用实时库,否则无法可靠地实现这一目标。

只是为了说明,像HotSpot这样的现代JVM中的Java线程实现依赖于主机操作系统的本机线程调度程序来决定运行什么线程。除非线程调度程序特别了解实时截止日期和内容,否则在决定运行什么线程时可能会采用“整个系统”视图。如果系统已加载,则任何特定线程可能无法安排运行数秒......或更长时间...在阻止其运行的条件(例如,等待计时器事件)之后已经过去。

然后存在Java GC可能导致所有其他线程阻塞的问题。

如果您真的需要Java的实时行为,那么它是可用的。例如:

但是,您应该更改应用程序以使用不同的API来为您提供实时行为。

答案 2 :(得分:7)

我不得不说,我认为其他两个答案目前对Java并发类的看法不尽如人意。它们不会给你毫秒级的准确度(“真正的”实时应用程序所期望的),但它们通常做得很好。我使用Futures和Executors编写了大规模的商业服务,他们通常在预期时间的10毫秒内工作,即使在负载下也是如此。

我在使用Java 1.6的MacOS 10.6和使用Java 1.6.0_22的WinXP上运行此测试,并且它们都按预期工作。

我按如下方式修改了代码以测试准确性:

    long time1 = System.nanoTime();

    System.out.println("Submitting");
    final Future<Object> future = exec.submit(call);
    try {
        future.get(1000, TimeUnit.MILLISECONDS);

        long time2 = System.nanoTime();
        System.out.println("No timeout after " + 
                             (time2-time1)/1000000000.0 + " seconds");

        fail("expected TimeoutException");
    } catch (TimeoutException ignore) {
        long time2 = System.nanoTime();
        System.out.println("Timed out after " +
                             (time2-time1)/1000000000.0 + " seconds");
    }
    finally {
        exec.shutdown();
    }

在XP中打印“在1.002598934秒后超时”,在MacOS X中打印“在1.003158秒后超时”。

如果原始海报会描述他们的操作系统和JDK版本,也许我们可以确定这是否是一个特定的错误。