Я пытаюсь использовать 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();
}
}
Вывод из этой программы, "Все прекрасно - нормальная ситуация!" даже при том, что единственное Выполнимое, отправленное пулу потоков, выдает исключение. Какой-либо ключ к разгадке того, что продолжается здесь?
Спасибо!
Из документов :
Примечание: когда действия заключены в задачи (например, FutureTask) либо явно, либо с помощью таких методов, как {{1} } submit, эти объекты задач улавливают и поддерживают вычислительные исключения и , чтобы они не вызывали резкого завершения, а внутренние исключения не передаются этому метод.
Когда вы отправляете 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);
}
}
}
Объяснение этого поведения прямо в javadoc для afterExecute :
Примечание. Когда действия заключены в задачи (например, FutureTask), либо явно или с помощью таких методов, как submit, эти объекты задач улавливают и поддерживают вычислительные исключения, а также , чтобы они не вызывали резкого завершения , а внутренние исключения не передаются этому методу .
ПРЕДУПРЕЖДЕНИЕ : следует отметить, что это решение блокирует вызывающий поток.
Если вы хотите обрабатывать исключения, создаваемые задачей, обычно лучше использовать Callable
, а не Runnable
.
Callable.call ()
может генерировать проверенные исключения, и они передаются обратно в вызывающий поток:
Callable task = ...
Future future = executor.submit(task);
try {
future.get();
} catch (ExecutionException ex) {
ex.getCause().printStackTrace();
}
Если Callable.call ()
генерирует исключение, это будет завернутый в ExecutionException
и выданный Future.get ()
.
Это, вероятно, будет намного предпочтительнее, чем создание подкласса ThreadPoolExecutor
. Это также дает вам возможность повторно отправить задачу, если исключение подлежит исправлению.
Вместо создания подкласса ThreadPoolExecutor я бы предоставил ему экземпляр ThreadFactory , который создает новые потоки и предоставляет им UncaughtExceptionHandler