我偶然发现了一个问题,可归纳如下:
当我手动创建线程时(即通过实例化java.lang.Thread
),UncaughtExceptionHandler
被适当地调用。但是,当我使用带有ExecutorService
的{{1}}时,处理程序会被省略。我错过了什么?
ThreadFactory
我希望:消息“未捕获异常......”的三倍
我得到:消息一次(由手动创建的线程触发)。
在Windows 7和Mac OS X 10.5上使用Java 1.6重现。
答案 0 :(得分:43)
因为例外不会被删除。
ThreadFactory生成的Thread没有直接给出Runnable或Callable。相反,您获得的Runnable是一个内部Worker类,例如,请参阅ThreadPoolExecutor $ Worker。在您的示例中,在给予newThread的Runnable上尝试System.out.println()
。
此Worker从您提交的作业中捕获任何RuntimeExceptions。
您可以使用ThreadPoolExecutor#afterExecute方法获取例外。
答案 1 :(得分:34)
提交给ExecutorService#submit
的任务抛出的异常会被包装到ExcecutionException
中并被Future.get()
方法重新抛出。这是因为执行者将异常视为任务结果的一部分。
如果您通过源自execute()
界面的Executor
方法提交任务,则会通知UncaughtExceptionHandler
。
答案 2 :(得分:23)
引用书 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来提交任务,控制台输出“在异常处理程序中”
答案 3 :(得分:6)
我只是浏览了我的旧问题,并认为我可以分享我实施的解决方案以防万一(或者我错过了一个错误)。
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);
}
}
答案 4 :(得分:4)
除了Thilos回答:我写了一篇关于这种行为的帖子,如果有人想让它解释得更详细一点:https://ewirch.github.io/2013/12/a-executor-is-not-a-thread.html。
以下是文章摘录:
一般来说,一个Thread只能处理一个Runable。当Thread.run()方法退出Thread死亡时。 ThreadPoolExecutor实现了一个技巧,使Thread进程多个Runnables:它使用自己的Runnable实现。线程正在使用Runnable实现启动,该实现从ExecutorService获取其他Runanbles(您的Runnables)并执行它们:ThreadPoolExecutor - &gt;线程 - &gt;工人 - &gt; YourRunnable。当您的Runnable实现中发生未捕获的异常时,它最终会在Worker.run()的finally块中结束。在这个finally块中,Worker类告诉ThreadPoolExecutor它“完成”了工作。该异常尚未到达Thread类,但ThreadPoolExecutor已将该worker注册为空闲。
这就是乐趣开始的地方。当所有Runnables都传递给Executor时,将调用awaitTermination()方法。这种情况很快发生,因此可能没有一个Runnables完成了他们的工作。如果发生异常,在异常到达Thread类之前,Worker将切换到“idle”。如果其他线程的情况类似(或者如果他们完成了工作),则所有Workers发出“idle”信号并返回awaitTermination()。主线程到达代码行,它检查收集的异常列表的大小。这可能发生在任何(或某些)线程有机会调用UncaughtExceptionHandler之前。它取决于执行的顺序,如果在主线程读取它之前,将在未捕获的异常列表中添加多少异常。
非常意外的行为。但如果没有可行的解决方案,我不会离开你。所以,让它成功。
我们很幸运,ThreadPoolExecutor类是为可扩展性而设计的。在执行之后有一个空的受保护方法(Runnable r,Throwable t)。这将在Runnable的run()方法之后直接调用,然后工作者发出完成工作的信号。正确的解决方案是扩展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); } }
答案 5 :(得分:2)
有一点解决方法。
在run
方法中,您可以捕获每个异常,然后执行类似的操作(例如:在finally
块中)
Thread.getDefaultUncaughtExceptionHandler().uncaughtException(Thread.currentThread(), ex);
//or, same effect:
Thread.currentThread().getUncaughtExceptionHandler().uncaughtException(Thread.currentThread(), ex);
这将“确保将当前异常的触发”抛出到你的uncoughtExceptionHandler(或defualt unought异常处理程序)。 您始终可以为池工作者重新提取捕获的异常。