Java执行任务有多次重试和超时

时间:2012-08-01 01:21:16

标签: java timeout

我正在尝试创建一个在最长时间内执行给定任务的方法。如果在那段时间内未能完成,应该在放弃之前重试多次。它也应该在每次尝试之间等待几秒钟。这就是我想出来的,我想对我的方法提出一些批评。使用ScheduledExecutorService这是一种更简单的方法吗?或者我这样做是否足够?

public static <T> T execute(Callable<T> task, int tries, int waitTimeSeconds, int timeout) 
    throws InterruptedException, TimeoutException, Exception {

    Exception lastThrown = null;
    for (int i = 0; i < tries; i++) {
        try {
            final Future<T> future = new FutureTask<T>(task);
            return future.get(timeout, TimeUnit.SECONDS);
        } catch (TimeoutException ex) {
            lastThrown = ex;
        } catch (ExecutionException ex) {
            lastThrown = (Exception) ex.getCause();
        }
        Thread.sleep(TimeUnit.SECONDS.toMillis(waitTimeSeconds));
    }
    if (lastThrown == null) {
        lastThrown = new TimeoutException("Reached max tries without being caused by some exception. " + task.getClass());
    }
    throw lastThrown;
}

2 个答案:

答案 0 :(得分:5)

我认为,但我认为,如果您正在安排与网络相关的任务,则不应重试,而应最终并行运行它们。我稍后会描述其他方法。

关于您的代码,您应该将任务传递给执行程序,或将FutureTask传递给线程。它不会产生一个线程或自己执行。如果你有一个执行者(参见ExecutorService),你甚至不需要FutureTask,你可以简单地安排它并获得一个可调用的。

因此,假设您有ExecutorService,则可以调用:

Future<T> future = yourExecutor.submit(task);

Future.get(timeout)将等待该超时并最终返回TimeoutException,即使该任务从未启动过,例如,如果Executor已经忙于完成其他工作而无法找到一个空闲线程。所以,你最终可能会尝试5次并等待几秒钟,而不会让任务有机会运行。这可能是也可能不是你所期望的,但通常不是。也许你应该等到它开始才能超时。

此外,即使它抛出TimeoutException,您也应该显式取消Future,否则它可能会继续运行,因为文档和代码都没有说当get with timeout失败时它会停止。

即使取消它,除非Callable被“正确写入”,否则它可能会继续运行一段时间。在这部分代码中你无法做到这一点,只要记住,没有任何线程可以“真正地停止”另一个线程在Java中所做的事情,并且有充分的理由。

但是我认为你的任务主要与网络有关,所以它应该对线程中断做出正确的反应。

我通常使用不同的策略是这样的情况:

  1. 我会写公共静态T执行(可调用任务,int maxTries,int timeout),所以任务,最大尝试次数(可能是1),最大总超时(“我希望答案最多10秒,无论如何你尝试了多少次,10秒或什么都没有“)
  2. 我开始产生任务,将其交给执行者,然后调用future.get(timeout / attempts)
  3. 如果我收到结果,请将其退回。如果我收到异常,将再试一次(见下文)
  4. 如果我得到超时,我不会取消将来,而是将其保存在列表中。
  5. 我检查是否已经过了太多时间,或者重试次数太多。在这种情况下,我取消列表中的所有期货并抛出异常,返回null,无论
  6. 否则,我循环,再次安排任务(与第一个任务并行)。
  7. 见第2点
  8. 如果我没有收到结果,我会检查列表中的未来,也许是之前产生的任务之一。
  9. 假设您的任务可以被执行多次(因为我认为它们是,否则无法重试),对于网络内容我发现此解决方案可以更好地工作。

    假设您的网络实际上非常繁忙,您要求网络连接,每次重试20秒。由于您的网络繁忙,20次重试中没有一次能够在2秒内完成连接。但是,持续40秒的单次执行可能会设法连接和接收数据。这就像一个人在网络缓慢的情况下强制性地在页面上按f5,它不会有任何好处,因为每次浏览器都必须从头开始。

    相反,我保持各种期货运行,第一个设法获取数据将返回结果,其他将停止。如果第一个挂起,第二个可以工作,或者第三个可能。

    与浏览器相比,就像打开另一个标签并重试在那里加载页面而不关闭第一个标签。如果网络很慢,第二个将花费一些时间,但不会停止第一个,最终将正确加载。如果相反第一个标签挂起,第二个标签将快速加载。无论哪个先加载,我们都可以关闭另一个标签。

答案 1 :(得分:1)

调用execute的线程会阻塞这么长时间。不确定这对你是否正确。基本上,对于这些类型的任务,ScheduledExecutorService是最好的。您可以安排任务并指定时间。看看ScheduledThreadPoolExecutor