在日常的开发工作当中,线程池往往承载着一个应用中最重要的业务逻辑,因此我们有必要更多地去关注线程池的执行情况,包括异常的处理和分析等。本文主要聚焦在如何正确使用线程池上,以及提供一些实用的建议。文中会稍微涉及到一些线程池实现原理方面的知识,但是不会过多展开。
线程池的异常处理
UncaughtExceptionHandler
我们都知道Runnable接口中的run方法是不允许抛出异常的,因此派生出这个线程的主线程可能无法直接获得该线程在执行过程中的异常信息。如下例:
public static void main(String[] args) throws Exception {
Thread thread = new Thread(() -> {
Uninterruptibles.sleepUninterruptibly(2, TimeUnit.SECONDS);
System.out.println(1 / 0); // 这行会导致报错!
});
thread.setUncaughtExceptionHandler((t, e) -> {
e.printStackTrace(); //如果你把这一行注释掉,这个程序将不会抛出任何异常.
});
thread.start();
}
为什么会这样呢?其实我们看一下Thread中的源码就会发现,Thread在执行过程中如果遇到了异常,会先判断当前线程是否有设置UncaughtExceptionHandler,如果没有,则会从线程所在的ThreadGroup中获取。
注意:每个线程都有自己的ThreadGroup,即使你没有指定,并且它实现了UncaughtExceptionHandler接口。
我们看下ThreadGroup中默认的对UncaughtExceptionHandler接口的实现:
public void uncaughtException(Thread t, Throwable e) { if (parent != null) { parent.uncaughtException(t, e); } else { Thread.UncaughtExceptionHandler ueh = Thread.getDefaultUncaughtExceptionHandler(); if (ueh != null) { ueh.uncaughtException(t, e); } else if (!(e instanceof ThreadDeath)) { System.err.print("Exception in thread \"" + t.getName() + "\" "); e.printStackTrace(System.err); } } }
这个ThreadGroup如果有父ThreadGroup,则调用父ThreadGroup的uncaughtException,否则调用全局默认的Thread.DefaultUncaughtExceptionHandler,如果全局的handler也没有设置,则只是简单地将异常信息定位到System.err中,这就是为什么我们应当在创建线程的时候,去实现它的UncaughtExceptionHandler接口的原因,这么做可以让你更方便地去排查问题。
通过execute提交任务给线程池
回到线程池这个话题,如果我们向线程池提交的任务中,没有对异常进行try...catch处理,并且运行的时候出现了异常,那会对线程池造成什么影响呢?答案是没有影响,线程池依旧可以正常工作,但是异常却被吞掉了。这通常来说不是一个好事情,因为我们需要拿到原始的异常对象去分析问题。
那么怎样才能拿到原始的异常对象呢?我们从线程池的源码着手开始研究这个问题。当然网上关于线程池的源码解析文章有很多,这里限于篇幅,直接给出最相关的部分代码:
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
while (task != null || (task = getTask()) != null) {
w.lock();
// If pool is stopping, ensure thread is interrupted;
// if not, ensure thread is not interrupted. This
// requires a recheck in second case to deal with
// shutdownNow race while clearing interrupt
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
beforeExecute(wt, task);
Throwable thrown = null;
try {
task.run();