Исключение исключений потоков из Java ExecutorService

Я работаю над основами разработки программного обеспечения для параллельных вычислений JavaSeis.org. Мне нужен надежный механизм для представления исключений потоков. Во время разработки знание о том, откуда происходят исключения, имеет большую ценность, поэтому я хотел бы ошибиться на стороне чрезмерной отчетности. Я также хотел бы иметь возможность обрабатывать тестирование Junit4 в потоках. Является ли подход ниже разумным или есть лучший способ?

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class TestThreadFailure {

  public static void main(String[] args) {
    int size = 1;
    ExecutorService exec = Executors.newFixedThreadPool(size);
    ThreadFailTask worker = new ThreadFailTask();
    Future<Integer> result = exec.submit(worker);
    try {
      Integer value = result.get();
      System.out.println("Result: " + value);
    } catch (Throwable t) {
      System.out.println("Caught failure: " + t.toString());
      exec.shutdownNow();
      System.out.println("Stack Trace:");
      t.printStackTrace();
      return;
    }
    throw new RuntimeException("Did not catch failure !!");
  }

  public static class ThreadFailTask implements Callable<Integer> {
    @Override
    public Integer call() {
      int nbuf = 65536;
      double[][] buf = new double[nbuf][nbuf];
      return new Integer((int) buf[0][0]);
    }
  }
}

Ответ 1

Я не верю, что есть стандартный "крючок", чтобы добраться до этих исключений при использовании submit(). Однако, если вам нужно поддерживать submit() (что звучит разумно, учитывая, что вы используете Callable), вы всегда можете обернуть Callables и Runnables:

ExecutorService executor = new ThreadPoolExecutor(1, 10, 60, TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>()) {
    @Override
    public <T> Future<T> submit(final Callable<T> task) {
        Callable<T> wrappedTask = new Callable<T>() {
            @Override
            public T call() throws Exception {
                try {
                    return task.call();
                }
                catch (Exception e) {
                    System.out.println("Oh boy, something broke!");
                    e.printStackTrace();
                    throw e;
                }
            }
        };

        return super.submit(wrappedTask);
    }
};

Конечно, этот метод работает только в том случае, если вы первый строите ExecutorService. Кроме того, не забудьте переопределить все три варианта submit().

Ответ 2

Рассмотрите возможность вызова execute() вместо submit() на ExecutorService. A Thread, вызываемый с помощью execute(), вызывается Thread.UncaughtExceptionHandler, когда он терпит неудачу.

Просто создайте ThreadFactory, который устанавливает Thread.UncaughtExceptionHandler на всех Threads, а затем вызывает вашу работу с execute() on ExecutorService вместо submit().

Посмотрите на этот связанный вопрос.

Ответ 3

Как объясняется в этом потоке В чем разница между методом submit и execute с ThreadPoolExecutor, использование execute будет работать только при реализации Runnable и not Callable, поскольку execute не может вернуться Будущее.

Я думаю, что в вашем сценарии вы должны построить будущий объект, чтобы он мог также разместить материал исключения. Поэтому в случае исключения вы создаете объект сообщения об ошибке.

Ответ 4

Мой первоначальный вопрос спросил, как реализовать "надежную" обработку исключений потоков с помощью Java ExecutorService. Благодаря Angelo и Greg за указатели на то, как обработка исключений работает с ExecutorService.submit() и Future.get(). Модифицированный фрагмент кода показан ниже. Ключевым моментом, который я узнал здесь, является то, что Future.get() ловит все исключения. Если поток был прерван или отменен, вы получите соответствующее исключение, в противном случае исключение будет завершено и повторно выбрано как ExecutionException.

import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class TestThreadFailure {

  public static void main(String[] args) {
    int size = 1;
    ExecutorService exec = Executors.newFixedThreadPool(size);
    ThreadFailTask worker = new ThreadFailTask();
    Future result = exec.submit(worker);
    try {
      Integer value = result.get();
      System.out.println("Result: " + value);
    } catch (ExecutionException ex) {
      System.out.println("Caught failure: " + ex.toString());
      exec.shutdownNow();
      return;
    } catch (InterruptedException iex) {
      System.out.println("Thread interrupted: " + iex.toString());
    } catch (CancellationException cex) {
      System.out.println("Thread cancelled: " + cex.toString());
    }
    exec.shutdownNow();
    throw new RuntimeException("Did not catch failure !!");
  }

  public static class ThreadFailTask implements Callable {
    @Override
    public Integer call() {
      int nbuf = 65536;
      double[][] buf = new double[nbuf][nbuf];
      return new Integer((int) buf[0][0]);
    }
  }
}

Ответ 5

Мне не повезло с другими ответами, потому что мне нужен фактический экземпляр исключения, сам по себе, а не только трассировка печатного стека. Для меня принятый ответ, включающий ThreadPoolExecutor # afterExecute() вопроса "Почему UncaughtExceptionHandler не вызванный ExecutorService?".

См. следующий пример кода:

List<Runnable> tasks = new LinkedList<>();
for (int i = 0; i < numThreads; ++i) {
    Runnable task = new Runnable() {
        @Override
        public void run() {
            throw new RuntimeException();
        }
    };

    tasks.add(task);
}

Optional<Throwable> opEmpty = Optional.empty();
/*
 * Use AtomicReference as a means of capturing the first thrown exception, since a
 * spawned thread can't "throw" an exception to the parent thread.
 */
final AtomicReference<Optional<Throwable>> firstThrownException =
        new AtomicReference<>(opEmpty);

/*
 * Use new ThreadPoolExecutor instead of Executors.newFixedThreadPool() so
 * that I can override afterExecute() for the purposes of throwing an
 * exception from the test thread if a child thread fails.
 */
ExecutorService execSvc = new ThreadPoolExecutor(numThreads, numThreads,
            0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()) {
    @Override
    public void afterExecute(Runnable task, Throwable failureCause) {
        if(failureCause == null) {
            // The Runnable completed successfully.
            return;
        }
        // only sets the first exception because it will only be empty on the first call.
        firstThrownException.compareAndSet(Optional.<Throwable>empty(), Optional.of(failureCause));
    }
};

for (Runnable task : tasks) {
    execSvc.execute(task);
}
execSvc.shutdown();
execSvc.awaitTermination(1, TimeUnit.HOURS);

assertEquals(firstThrownException.get(), Optional.empty());

Ответ 6

Для обработки исключений в ExecutorService вам необходимо воспользоваться Callable и Будущее.

Подробнее см. ниже видео. Надеюсь, это поможет вам.

ВИДЕО: Callable and Future (11 мин)