Почему 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.");
}
}
Я ожидаю: три раза сообщение "Неотключить исключение..."
Я получаю: одно сообщение (вызванное созданным вручную потоком).
Воспроизводится с помощью Java 1.6 в Windows 7 и Mac OS X 10.5.
Ответы
Ответ 1
Потому что исключение не идет неотображаемым.
Поток, который производит ваш ThreadFactory, напрямую не передается вашим Runnable или Callable. Вместо этого Runnable, который вы получаете, является внутренним классом Worker, например, см. ThreadPoolExecutor $Worker. Попробуйте System.out.println()
в Runnable, заданном newThread в вашем примере.
Этот Рабочий ловит любые RuntimeExceptions из вашего отправленного задания.
Вы можете получить исключение в методе ThreadPoolExecutor # afterExecute.
Ответ 2
Исключения, которые выполняются задачами, переданными в ExecutorService#submit
, завершаются в ExcecutionException
и возвращаются методом Future.get()
. Это потому, что исполнитель рассматривает исключение как часть результата задачи.
Если вы отправляете задачу с помощью метода execute()
, который исходит из интерфейса Executor
, уведомляется UncaughtExceptionHandler
.
Ответ 3
Цитата из книги Java Concurrency на практике (стр. 163), надеюсь, что это поможет
Несколько смехотворно, исключения, отбрасываемые из задач, превращаются в невкусные обработчик исключений только для задач, отправленных с выполнением; для поставленных задач с подачей, любое исключенное исключение, проверенное или нет, считается частью статус возврата задачи. Если задание, отправленное с отправлением, завершается с исключением, он заново свернут 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 для отправки задачи, а выходы консоли "в обработчике исключений"
Ответ 4
Я только просматривал свои старые вопросы и думал, что могу поделиться решением, которое я реализовал, в случае, если он помогает кому-то (или я пропустил ошибку).
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 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);
}
}
Ответ 5
Существует немного обходного пути.
В вашем методе запуска вы можете поймать каждое исключение, а затем сделать что-то вроде этого
Thread.getDefaultUncaughtExceptionHandler().uncaughtException(Thread.currentThread(), ex);
//or
Thread.currentThread().getUncaughtExceptionHandler().uncaughtException(Thread.currentThread(), ex);
Это будет "зарегистрировать" текущее исключение, которое будет передано вашему uncoughttception обработчику исключений.
Вы всегда можете отменить захваченные исключения для рабочего пула.
Ответ 6
В дополнение к ответу Тилоса: я написал сообщение об этом поведении, если он хочет, чтобы он объяснил немного более подробный: https://ewirch.github.io/2013/12/a-executor-is-not-a-thread.html