【问题标题】:How to catch an exception when any Thread/Runnable/Callable in an ExecutorService fails while awaiting termination当 ExecutorService 中的任何 Thread/Runnable/Callable 在等待终止时失败时如何捕获异常
【发布时间】:2015-09-30 12:04:53
【问题描述】:

我目前的代码如下所示:

public void doThings() {
    int numThreads = 4;
    ExecutorService threadPool = Executors.newFixedThreadPool(numThreads);
    for (int i = 0; i < numThreads; i++) {

        final int index = i;
        Runnable runnable = () -> {

            // do things based on index
        };

        threadPool.execute(runnable);

    }

    threadPool.shutdown();

    try {
        // I'd like to catch exceptions here from any of the runnables
        threadPool.awaitTermination(1, TimeUnit.HOURS);
    } catch (InterruptedException e) {
        Utils.throwRuntimeInterruptedException(e);
    }
}

基本上,我会并行创建很多工作,然后等待全部完成。如果任何处理失败,我需要快速知道并中止这一切。 threadPool.awaitTermination 似乎没有注意到是否在其中一个线程内引发了异常。我只是在控制台中看到一个堆栈跟踪。

我对并发了解不多,所以我对所有可用的接口/对象有点迷失,例如CallableFutureTask 等。

我看到threadPool.invokeAll(callables) 会给我一个List&lt;Future&gt;Future.get() 可以从线程内抛出异常,但是如果我调用它(如果可调用对象在它自己的线程中抛出异常)。但是,如果我 .get 在顺序集合中拥有每个可调用对象,那么在所有其他对象都完成之前,我不会知道最后一个对象是否失败。

我最好的猜测是有一个队列,可运行对象在其上放置 Boolean 表示成功或失败,然后将 take() 从队列中放入与线程数一样多的次数。

对于一个看似非常常见、简单的用例,我觉得这太复杂了(即使只是我粘贴的代码也有点长得惊人)。这甚至不包括在失败时中止可运行文件。必须有更好的方法,作为初学者我不知道。

【问题讨论】:

  • 可以使用shutdownNow()方法停止所有线程。当其中一个操作失败时调用此方法。
  • 谢谢,这会有所帮助。我想我应该确保每个可运行对象都定期运行 if (Thread.currentThread().isInterrupted()) throw new RuntimeException();?
  • 只有在使用 Callable 时才可能出现异常。对于 Runnable,return 语句应该足够了吗?
  • @Johannes 否,因为我要检查中断的地方在深处,而不是直接在 run 方法中。
  • 您可以使用共享的“errorFlag”并中止操作(如果已设置)。这是关于向其他线程发出信号的部分。当然,您必须定期检查它,但无论如何必须同样支持中断。要设置它,您可以在run 中使用一个大的try/catch 包围您的所有代码,在catch 中设置errorFlag。这就是我要做的。

标签: java multithreading exception-handling future executorservice


【解决方案1】:

我最终发现ExecutorCompletionService 就是为此而设计的。然后我编写了以下类来抽象过程并简化使用:

import java.util.Iterator;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Wrapper around an ExecutorService that allows you to easily submit Callables, get results via iteration,
 * and handle failure quickly. When a submitted callable throws an exception in its thread this
 * will result in a RuntimeException when iterating over results. Typical usage is as follows:
 *
 * <ol>
 *     <li>Create an ExecutorService and pass it to the constructor.</li>
 *     <li>Create Callables and ensure that they respond to interruption, e.g. regularly call: <pre>{@code
 *     if (Thread.currentThread().isInterrupted()) {
           throw new RuntimeException("The thread was interrupted, likely indicating failure in a sibling thread.");
 *     }}</pre></li>
 *     <li>Pass the callables to the submit() method.</li>
 *     <li>Call finishedSubmitting().</li>
 *     <li>Iterate over this object (e.g. with a foreach loop) to get results from the callables.
 *     Each iteration will block waiting for the next result.
 *     If one of the callables throws an unhandled exception or the thread is interrupted during iteration
 *     then ExecutorService.shutdownNow() will be called resulting in all still running callables being interrupted,
 *     and a RuntimeException will be thrown </li>
 * </ol>
 */
public class ExecutorServiceResultsHandler<V> implements Iterable<V> {

    private ExecutorCompletionService<V> completionService;
    private ExecutorService executorService;
    AtomicInteger taskCount = new AtomicInteger(0);

    public ExecutorServiceResultsHandler(ExecutorService executorService) {
        this.executorService = executorService;
        completionService = new ExecutorCompletionService<V>(executorService);
    }

    public void submit(Callable<V> task) {
        completionService.submit(task);
        taskCount.incrementAndGet();
    }

    public void finishedSubmitting() {
        executorService.shutdown();
    }

    @Override
    public Iterator<V> iterator() {
        return new Iterator<V>() {
            @Override
            public boolean hasNext() {
                return taskCount.getAndDecrement() > 0;
            }

            @Override
            public V next() {
                Exception exception;
                try {
                    return completionService.take().get();
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    exception = e;
                } catch (ExecutionException e) {
                    exception = e;
                }
                executorService.shutdownNow();
                executorService = null;
                completionService = null;
                throw new RuntimeException(exception);
            }
        };
    }

    /**
     * Convenience method to wait for the callables to finish for when you don't care about the results.
     */
    public void awaitCompletion() {
        for (V ignored : this) {
            // do nothing
        }
    }

}

【讨论】:

    猜你喜欢
    • 2019-10-07
    • 2013-12-01
    • 2021-11-09
    • 1970-01-01
    • 1970-01-01
    • 2018-07-24
    • 2015-07-18
    • 2016-04-16
    • 2021-12-17
    相关资源
    最近更新 更多