处理Java ExecutorService任务中的异常

Tom*_*Tom 201 java multithreading exception executorservice threadpoolexecutor

我正在尝试使用Java的ThreadPoolExecutor类来运行具有固定数量线程的大量重量级任务.每个任务都有许多地方,在这些地方可能因异常而失败.

我已经进行了子类化,ThreadPoolExecutor并且我已经覆盖了该afterExecute方法,该方法应该在运行任务时提供任何未捕获的异常.但是,我似乎无法使其发挥作用.

例如:

public class ThreadPoolErrors extends ThreadPoolExecutor {
    public ThreadPoolErrors() {
        super(  1, // core threads
                1, // max threads
                1, // timeout
                TimeUnit.MINUTES, // timeout units
                new LinkedBlockingQueue<Runnable>() // work queue
        );
    }

    protected void afterExecute(Runnable r, Throwable t) {
        super.afterExecute(r, t);
        if(t != null) {
            System.out.println("Got an error: " + t);
        } else {
            System.out.println("Everything's fine--situation normal!");
        }
    }

    public static void main( String [] args) {
        ThreadPoolErrors threadPool = new ThreadPoolErrors();
        threadPool.submit( 
                new Runnable() {
                    public void run() {
                        throw new RuntimeException("Ouch! Got an error.");
                    }
                }
        );
        threadPool.shutdown();
    }
}
Run Code Online (Sandbox Code Playgroud)

这个程序的输出是"一切都很好 - 情况正常!" 即使提交给线程池的唯一Runnable也会引发异常.有什么线索在这里发生了什么?

谢谢!

ska*_*man 241

警告:应该注意,此解决方案将阻止调用线程.


如果要处理任务抛出的异常,那么通常最好使用Callable而不是Runnable.

Callable.call() 允许抛出已检查的异常,并将这些异常传播回调用线程:

Callable task = ...
Future future = executor.submit(task);
try {
   future.get();
} catch (ExecutionException ex) {
   ex.getCause().printStackTrace();
}
Run Code Online (Sandbox Code Playgroud)

如果Callable.call()抛出一个异常,它将被包装在一个ExecutionException并被抛出Future.get().

这可能比继承更好ThreadPoolExecutor.如果异常是可恢复的,它还为您提供重新提交任务的机会.

  • 不要使用此解决方案,因为它打破了使用ExecutorService的全部目的.ExecutorService是一种异步执行机制,能够在后台执行任务.如果在执行后立即调用future.get(),它将阻塞调用线程,直到任务完成. (35认同)
  • 它是完美的,但如果我并行运行任务并且不想阻止执行该怎么办? (15认同)
  • _> Callable.call()被允许抛出已检查的异常,并将这些异常传播回调用线程:_注意,只有当`future.get()`或其重载版本为is时,抛出的异常才会传播到调用线程.调用. (4认同)
  • 同意.这应该是公认的答案. (2认同)
  • 该解决方案不应受到如此高的评价。Future.get() 同步工作,并将充当阻塞器,直到 Runnable 或 Callable 被执行,并且如上所述违背了使用 Executor 服务的目的 (2认同)
  • 正如#nhylated指出的,这值得一个jdk BUG。如果未调用Future.get(),则将静默忽略Callable的任何未捕获异常。设计非常糟糕。。。仅仅花了1天以上的时间来弄清楚一个库使用了这个库,而jdk却默默地忽略了异常。并且,它仍然存在于jdk12中。 (2认同)

nos*_*nos 149

来自文档:

注意:当任务明确地或通过诸如submit之类的方法包含在任务(例如FutureTask)中时,这些任务对象会捕获并维护计算异常,因此它们不会导致突然终止,并且内部异常不会传递给此方法.

当你提交一个Runnable时,它将被包含在Future中.

你的afterExecute应该是这样的:

public final class ExtendedExecutor extends ThreadPoolExecutor {

    // ...

    protected void afterExecute(Runnable r, Throwable t) {
        super.afterExecute(r, t);
        if (t == null && r instanceof Future<?>) {
            try {
                Future<?> future = (Future<?>) r;
                if (future.isDone()) {
                    future.get();
                }
            } catch (CancellationException ce) {
                t = ce;
            } catch (ExecutionException ee) {
                t = ee.getCause();
            } catch (InterruptedException ie) {
                Thread.currentThread().interrupt();
            }
        }
        if (t != null) {
            System.out.println(t);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 谢谢,我最终使用了这个解决方案.另外,如果有人感兴趣:其他人建议不要继承ExecutorService,但我还是做了,因为我想在完成任务时监视任务,而不是等待所有任务终止,然后在所有返回的Futures上调用get() . (6认同)
  • 我们是否必须使用“future.isDone()”检查 future 是否完成?由于“afterExecute”是在“Runnable”完成后运行的,所以我假设“future.isDone()”总是返回“true”。 (4认同)
  • 子类化执行程序的另一种方法是子类化 FutureTask 并覆盖其“完成”方法 (2认同)

Dre*_*lls 17

这种行为的解释正好在afterExecutejavadoc中:

注意:当任务明确地或通过诸如submit之类的方法包含在任务(例如FutureTask)中时,这些任务对象会捕获并维护计算异常,因此它们不会导致突然终止,并且内部异常不会传递给此方法.


mom*_*omo 10

我通过将提交的runnable包装提交给执行程序来解决它.

CompletableFuture.runAsync(

        () -> {
                try {
                        runnable.run();
                } catch (Throwable e) {
                        Log.info(Concurrency.class, "runAsync", e);
                }
        },

        executorService
);
Run Code Online (Sandbox Code Playgroud)

  • 您可以使用CompletableFuture的whenComplete()方法来提高可读性。 (2认同)

yeg*_*256 6

我正在使用VerboseRunnable来自jcabi-log的类,它会吞下所有异常并记录它们.非常方便,例如:

import com.jcabi.log.VerboseRunnable;
scheduler.scheduleWithFixedDelay(
  new VerboseRunnable(
    Runnable() {
      public void run() { 
        // the code, which may throw
      }
    },
    true // it means that all exceptions will be swallowed and logged
  ),
  1, 1, TimeUnit.MILLISECONDS
);
Run Code Online (Sandbox Code Playgroud)


CSc*_*ulz 5

另一个解决方案是使用ManagedTaskManagedTaskListener

您需要一个实现ManagedTask接口的CallableRunnable

该方法getManagedTaskListener返回您想要的实例。

public ManagedTaskListener getManagedTaskListener() {
Run Code Online (Sandbox Code Playgroud)

然后您在ManagedTaskListener中实现该taskDone方法:

@Override
public void taskDone(Future<?> future, ManagedExecutorService executor, Object task, Throwable exception) {
    if (exception != null) {
        LOGGER.log(Level.SEVERE, exception.getMessage());
    }
}
Run Code Online (Sandbox Code Playgroud)

有关托管任务生命周期和侦听器的更多详细信息。