在使用 JUC 的线程池时(ThreadPoolExecutor
等),需要注意执行任务时可能会抛出的各种 RuntimeException
。如果直接使用 JDK 提供的线程池实现的时候,很有可能发生任务“悄无声息”、“莫名其妙”地就结束了。其实,这种情况的发生是因为你没用捕获并处理 Runnable
或 Callable
中发生的异常。
在你写命令行程序的时候,上述情况并不会发生。那是因为线程的默认 UncaughtExceptionHandler
会将异常栈信息输出到命令行界面上,所以大家都知道任务因为异常的发生而退出。但是,在服务器应用中,没有人会一直去看控制台输出。这时,如果还是使用默认的 UncaughtExceptionHandler
就不合适了。
如何解决未捕获异常信息丢失的问题
方法一:自定义 ThreadFactory
话不多说,直接看代码
public class LogExceptionThreadFactory implements ThreadFactory {
private static final Logger LOGGER = LoggerFactory.getLogger("DEFAULT LOGGER");
private static final ThreadFactory DEFAULT_THREAD_FACTORY = Executors.defaultThreadFactory();
@Override
public Thread newThread(Runnable r) {
if (r == null) throw new NullPointerException();
Thread t = DEFAULT_THREAD_FACTORY.newThread(r);
t.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
LOGGER.error("Uncaught Exception:", e);
}
});
return t;
}
}
然后你可以通过 Executors
中的工具方法使用这个自定义的 ThreadFactory
:
ExecutorService executorService = Executors.newSingleThreadExecutor(new LogExceptionThreadFactory());
方法二:继承 ThreadPoolExecutor,Override newTaskFor
方法
从 JDK 6 开始,AbstractExecutorService
中就加入了一个新方法 newTaskFor
。通过这个工厂方法,你可以对传入的 Runnable
或 Callable
对象做一些封装。默认的实现是封装成 FutureTask
。为了解决未捕获异常丢失的问题,你一再封装一层,在调用 run
或 call
方法之外捕获异常并处理。
PS. ThreadPoolExecutor
中 execute
和 submit
方法在未捕获异常处理方面的不同
如果按照方法一,在调用 execute
方法时,未捕获的运行时异常信息就会被处理。但是如果是用 submit
方法,异常信息会被封装到 FutureTask.get()
方法所抛出的 ExecutionException
中。也就是说,如果使用 submit
不调用 FutureTask.get
方法,你还是无法得到未捕获异常信息。对于这一点区别,你需要额外注意。