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

Posted

技术标签:

【中文标题】为啥 Java Future.get(timeout) 不可靠?【英文标题】:Why is Java Future.get(timeout) Not Reliable?为什么 Java Future.get(timeout) 不可靠? 【发布时间】:2011-05-20 01:16:47 【问题描述】:

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) 
    

【问题讨论】:

有趣。是否因平台而异?这可能是 JVM 或操作系统中的错误。 我不确定这本身是否真的算作一个错误。在启动另一个线程甚至开始等待 Future 完成之间将经过多少时间肯定会有一些不确定性。是的,1 秒似乎有点多,但我在文档中没有看到任何实际上对截止日期做出硬性保证的内容。 我只在 Windows XP 上试过。我同意可能存在不确定性,但是在这个例子中没有发生太多事情。我的真实案例有多个线程,并且在 JNI 中对网络响应有阻塞。在这种情况下,我可以理解超时的一些变化,但在这里? 您使用的是什么 JVM/JDK/IDE/TestFramework?这在 Sun JDK6 + jUnit4 + Netbeans 中运行良好。 @Seh 指出这可能是线程争用问题:如果您使用 FutureTask 将测试更改为在单个线程中运行会发生什么? 【参考方案1】:

我不得不说,我认为其他两个答案目前对 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 版本,也许我们可以确定这是否是一个特定的错误。

【讨论】:

尝试在已加载的系统上运行它,看看它是否准确。在好的日子里它可以工作,在糟糕的日子里它不会,所以不要编写你的应用程序(或单元测试)来依赖它。 @Stephen-C - 当然我不会依赖于在生产应用程序中总是获得完美的时间,但我实际上已经在一个非常大的 dot-com 中将这样的代码用于时间关键服务并且对于至少 99.9% 的情况,它按要求运行(我们有约 50 毫秒的安全边际)。 我在 Mac OSX 10.6.5 和 Java 1.6 上尝试过,它可以工作。在办公室,我使用的是 Windows XP Sun Java 1.6 - 不确定次要版本,但它是最新的。星期五失败了。我确实注意到我的实际代码在大多数情况下都会出现这种不可靠的超时,但我确实有一天超时会在几毫秒内发生。 Future.get() 中指定的时间。 感谢大家的精彩讨论。我的系统可以处理超时的可变性(我认为)。它是断路器模式的一种实现。只要超时确实最终发生,断路器至少在增加价值。我将等到这段代码进入性能测试环境,看看那里会发生什么。很高兴看到它的行为类似于@andrewmu 的系统...... 作为参考,我提到的系统运行在 Redhat Linux(可能是 2.4 内核)和 Java 1.5 上。它似乎不太可能(尽管并非不可能)在 Java 1.6 中退步很多【参考方案2】:

@seh 是对的。

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

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

那么就有Java GC可能导致所有其他线程阻塞的问题。

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

Sun Java Real-Time System 页面。 ***Real time Java 页面。

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

【讨论】:

【参考方案3】:

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

在您的情况下,我们可以假设在Executor 中运行的线程获得焦点,而通过test() 运行的主线程处于暂停状态,尽管处于可运行状态。至于观察到的将提交的任务延迟 2 秒和 3 秒之间的差异,我希望您会发现甚至 3 秒都不够的情况,这取决于其他进程在您的计算机上忙于做什么。

【讨论】:

以上是关于为啥 Java Future.get(timeout) 不可靠?的主要内容,如果未能解决你的问题,请参考以下文章

java如何实现一个Future

Java并发编程-扩展可回调的Future

java并发编程之Future.get() 在线程池配置RejectedExecutionHandler为ThreadPoolExecutor.DiscardPolicy策略时一直阻塞

java并发编程之Future.get() 在线程池配置RejectedExecutionHandler为ThreadPoolExecutor.DiscardPolicy策略时一直阻塞

java并发编程之Future.get() 在线程池配置RejectedExecutionHandler为ThreadPoolExecutor.DiscardPolicy策略时一直阻塞

java8 之CompletableFuture -- 如何构建异步应用