深度解析Java线程池的异常处理

在逛同性交友网站GitHub的时候看到一个解析线程池异常处理的Issue,正好是曾经自己遇到过的问题。在此记录下来,并将其拓展到其他类型的线程池。

本文因篇幅省略了诸多AQS相关知识,可以查看博客中另一篇博文 一行一行源码分析清楚AQS 以保证清楚理解本文。

1、ThreadPoolExecutor

这一线程池由来已久,是抽象类 AbstractExecutorService 继承类,通过调用不同构造函数实现诸如 newFixedThreadPool 、newCachedThreadPool 线程池功能。

问题:

考虑下面这段代码,有什么区别呢?你可以猜猜会不会有异常打出呢?如果打出来的话是在哪里?:

你会发现单就执行这两句的话,结果只会打印一处异常信息,来源于 execute() 中的 obj.toString() 。

源码解析:

分析下面源码,发现其实重载的 submit() 方法将 Runnable、Callable 都封装成继承 Future 的 RunnableFuture 的实现类 FutureTask 对象(有点忽悠  XD)

接下来就会实际提交到队列中交给线程池调度处理:

那么接下来看看线程池核心的流程:

submit() 的方式

之前我们知道最终传递过去的是FutureTask,也就是说会调用这里的 Future 的 run方法,我们看看实现:

如上面分析到的,这样的话调用 FutureTask.run() 并不会直接抛出异常,所以在 ThreadPool.execute() 中捕获不到异常。但我们可以通过调用 get() 方法来捕捉异常。

submit() 解决方式

1、基本方式 try/catch,直接调用get()

2、重写  protected afterExecute(Runnable r,Throwable t ) { } 方法

想想如果我明明一开始调用的是 submit(Runnable r) ,为了捕捉异常还需刻意调用 get() 未免有点麻烦。Doug Lea 大佬已经在 JDK 文档中教我们可以重写 ThreadPoolExecutor 中的 afterExecute() 方法来实现异常捕获:

execute() 方式:

如代码SrcAnalyse2,此方法不同于submit() 会进行封装成Future ,其传递过去的就直接是Runnable,因此就会直接抛出:

那么这里的异常到底会抛出到哪里呢, 我们看看JVM具体是怎么处理的:

可以看到这里最终会去调用Thread#dispatchUncaughtException方法:

execute() 解决方式

1、基本方式,直接try/catch

2、线程重写 setUncaughtExceptionHandler() 方法

 

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*

%d 博主赞过: