Почему UncaughtExceptionHandler не вызывается ExecutorService?
Я наткнулся на проблему, которая может быть кратко изложена следующим образом:
Когда я создаю поток вручную (т.е. путем создания экземпляра java.lang.Thread
) UncaughtExceptionHandler
называется соответствующим образом. Тем не менее, когда я использую ExecutorService
с ThreadFactory
обработчик опущен. Что я упустил?
public class ThreadStudy {
private static final int THREAD_POOL_SIZE = 1;
public static void main(String[] args) {
// create uncaught exception handler
final UncaughtExceptionHandler exceptionHandler = new UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
synchronized (this) {
System.err.println("Uncaught exception in thread '" + t.getName() + "': " + e.getMessage());
}
}
};
// create thread factory
ThreadFactory threadFactory = new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
// System.out.println("creating pooled thread");
final Thread thread = new Thread(r);
thread.setUncaughtExceptionHandler(exceptionHandler);
return thread;
}
};
// create Threadpool
ExecutorService threadPool = Executors.newFixedThreadPool(THREAD_POOL_SIZE, threadFactory);
// create Runnable
Runnable runnable = new Runnable() {
@Override
public void run() {
// System.out.println("A runnable runs...");
throw new RuntimeException("Error in Runnable");
}
};
// create Callable
Callable<Integer> callable = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
// System.out.println("A callable runs...");
throw new Exception("Error in Callable");
}
};
// a) submitting Runnable to threadpool
threadPool.submit(runnable);
// b) submit Callable to threadpool
threadPool.submit(callable);
// c) create a thread for runnable manually
final Thread thread_r = new Thread(runnable, "manually-created-thread");
thread_r.setUncaughtExceptionHandler(exceptionHandler);
thread_r.start();
threadPool.shutdown();
System.out.println("Done.");
}
}
Я ожидаю: три раза сообщение "Uncaught исключения..."
Я получаю: сообщение один раз (вызвано созданным вручную потоком).
Воспроизводится с Java 1.6 на Windows 7 и Mac OS X 10.5.
6 ответов
Потому что исключение не осталось безнаказанным.
Поток, созданный вашей ThreadFactory, не получает ваш Runnable или Callable напрямую. Вместо этого Runnable, который вы получаете, является внутренним классом Worker, например, смотрите ThreadPoolExecutor$Worker. Пытаться System.out.println()
на Runnable, переданном newThread в вашем примере.
Этот работник отлавливает любые исключения RuntimeException из представленной вами работы.
Вы можете получить исключение в методе ThreadPoolExecutor#afterExecute.
Исключения, которые выбрасываются заданиями, представленными ExecutorService#submit
быть завернутым в ExcecutionException
и свергнуты Future.get()
метод. Это потому, что исполнитель рассматривает исключение как часть результата задачи.
Однако если вы отправите задание через execute()
метод, который происходит от Executor
интерфейс, UncaughtExceptionHandler
уведомлен.
Цитата из книги Java Concurrency in Practice(стр. 163), надеюсь, это поможет
Несколько странно, что исключения, выдаваемые из задач, делают его обработчиком необработанных исключений только для задач, переданных с execute; для задач, отправленных с отправкой, любое выброшенное исключение, проверенное или нет, считается частью статуса возврата задачи. Если задача, отправленная с submit, завершается с исключением, она перебрасывается Future.get, обернутым в ExecutionException.
Вот пример:
public class Main {
public static void main(String[] args){
ThreadFactory factory = new ThreadFactory(){
@Override
public Thread newThread(Runnable r) {
// TODO Auto-generated method stub
final Thread thread =new Thread(r);
thread.setUncaughtExceptionHandler( new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
// TODO Auto-generated method stub
System.out.println("in exception handler");
}
});
return thread;
}
};
ExecutorService pool=Executors.newSingleThreadExecutor(factory);
pool.execute(new testTask());
}
private static class testTask implements Runnable {
@Override
public void run() {
// TODO Auto-generated method stub
throw new RuntimeException();
}
}
Я использую execute для отправки задачи и выводов консоли "в обработчике исключений"
Я просто просмотрел свои старые вопросы и подумал, что могу поделиться решением, которое я реализовал, на случай, если оно кому-нибудь поможет (или я пропустил ошибку).
import java.lang.Thread.UncaughtExceptionHandler;
import java.util.concurrent.Callable;
import java.util.concurrent.Delayed;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.FutureTask;
import java.util.concurrent.RunnableScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
/**
* @author Mike Herzog, 2009
*/
public class ExceptionHandlingExecuterService extends ScheduledThreadPoolExecutor {
/** My ExceptionHandler */
private final UncaughtExceptionHandler exceptionHandler;
/**
* Encapsulating a task and enable exception handling.
* <p>
* <i>NB:</i> We need this since {@link ExecutorService}s ignore the
* {@link UncaughtExceptionHandler} of the {@link ThreadFactory}.
*
* @param <V> The result type returned by this FutureTask's get method.
*/
private class ExceptionHandlingFutureTask<V> extends FutureTask<V> implements RunnableScheduledFuture<V> {
/** Encapsulated Task */
private final RunnableScheduledFuture<V> task;
/**
* Encapsulate a {@link Callable}.
*
* @param callable
* @param task
*/
public ExceptionHandlingFutureTask(Callable<V> callable, RunnableScheduledFuture<V> task) {
super(callable);
this.task = task;
}
/**
* Encapsulate a {@link Runnable}.
*
* @param runnable
* @param result
* @param task
*/
public ExceptionHandlingFutureTask(Runnable runnable, RunnableScheduledFuture<V> task) {
super(runnable, null);
this.task = task;
}
/*
* (non-Javadoc)
* @see java.util.concurrent.FutureTask#done() The actual exception
* handling magic.
*/
@Override
protected void done() {
// super.done(); // does nothing
try {
get();
} catch (ExecutionException e) {
if (exceptionHandler != null) {
exceptionHandler.uncaughtException(null, e.getCause());
}
} catch (Exception e) {
// never mind cancelation or interruption...
}
}
@Override
public boolean isPeriodic() {
return this.task.isPeriodic();
}
@Override
public long getDelay(TimeUnit unit) {
return task.getDelay(unit);
}
@Override
public int compareTo(Delayed other) {
return task.compareTo(other);
}
}
/**
* @param corePoolSize The number of threads to keep in the pool, even if
* they are idle.
* @param eh Receiver for unhandled exceptions. <i>NB:</i> The thread
* reference will always be <code>null</code>.
*/
public ExceptionHandlingExecuterService(int corePoolSize, UncaughtExceptionHandler eh) {
super(corePoolSize);
this.exceptionHandler = eh;
}
@Override
protected <V> RunnableScheduledFuture<V> decorateTask(Callable<V> callable, RunnableScheduledFuture<V> task) {
return new ExceptionHandlingFutureTask<V>(callable, task);
}
@Override
protected <V> RunnableScheduledFuture<V> decorateTask(Runnable runnable, RunnableScheduledFuture<V> task) {
return new ExceptionHandlingFutureTask<V>(runnable, task);
}
}
В дополнение к ответу Thilos: я написал пост об этом поведении, если кто-то хочет, чтобы он объяснил немного более подробно: https://ewirch.github.io/2013/12/a-executor-is-not-a-thread.html.
Вот выдержки из статьи:
Поток способен обрабатывать только один Runable в целом. При выходе из метода Thread.run() поток умирает. ThreadPoolExecutor реализует трюк, чтобы заставить поток обрабатывать несколько Runnables: он использует собственную реализацию Runnable. Потоки запускаются с реализацией Runnable, которая выбирает другие Runanbles (ваши Runnables) из ExecutorService и выполняет их: ThreadPoolExecutor -> Thread -> Worker -> YourRunnable. Когда в вашей реализации Runnable возникает неперехваченное исключение, оно попадает в блок finally Worker.run(). В этом окончательном блоке класс Worker сообщает ThreadPoolExecutor, что он "завершил" работу. Исключение еще не поступило в класс Thread, но ThreadPoolExecutor уже зарегистрировал работника как бездействующий.
И вот тут начинается самое интересное. Метод awaitTermination() будет вызван, когда все Runnables будут переданы Исполнителю. Это происходит очень быстро, так что, вероятно, ни один из Runnables не закончил свою работу. Работник переключится в режим ожидания, если возникнет исключение, прежде чем исключение достигнет класса Thread. Если ситуация аналогична для других потоков (или если они закончили свою работу), все работники сигнализируют "бездействует" и возвращает awaitTermination(). Основной поток достигает строки кода, где он проверяет размер собранного списка исключений. И это может произойти до того, как какой-либо (или какой-либо) из потоков сможет вызвать UncaughtExceptionHandler. Это зависит от порядка выполнения, если или сколько исключений будет добавлено в список необработанных исключений до того, как основной поток его прочитает.
Очень неожиданное поведение. Но я не оставлю тебя без рабочего решения. Итак, давайте заставим это работать.
Нам повезло, что класс ThreadPoolExecutor был разработан для расширяемости. Существует пустой защищенный метод afterExecute(Runnable r, Throwable t). Это будет вызвано непосредственно после метода run () нашего Runnable, прежде чем работник сообщит, что он завершил работу. Правильным решением является расширение ThreadPoolExecutor для обработки необработанных исключений:
public class ExceptionAwareThreadPoolExecutor extends ThreadPoolExecutor { private final List<Throwable> uncaughtExceptions = Collections.synchronizedList(new LinkedList<Throwable>()); @Override protected void afterExecute(final Runnable r, final Throwable t) { if (t != null) uncaughtExceptions.add(t); } public List<Throwable> getUncaughtExceptions() { return Collections.unmodifiableList(uncaughtExceptions); } }
Есть немного обходного пути. В вашем run
метод, вы можете поймать каждое исключение, а затем сделать что-то вроде этого (например, в finally
блок)
Thread.getDefaultUncaughtExceptionHandler().uncaughtException(Thread.currentThread(), ex);
//or, same effect:
Thread.currentThread().getUncaughtExceptionHandler().uncaughtException(Thread.currentThread(), ex);
Это "обеспечит срабатывание" текущего исключения, которое выдается вашему uncoughtExceptionHandler (или обработчику невыплаченных исключений defualt). Вы всегда можете выбросить пойманные исключения для работника пула.