关于线程的异常,我们可以先看一个小栗子🌰:
fun main(args: Array<String>) {
println("-------1---主线程开始执行")
try{
Thread{
println("-------2---子线程开始执行")
throw Exception("error!!!")
println("-------3---子线程异常后执行")
}.start()
}catch (e: Exception){
println("-------4--我捕获了异常")
}
println("-------4---主线程开启子线程后执行")
Thread.sleep(1000)
println("-------5---主线程 sleep 后执行")
}
执行结果:
可以初步得出以下结论:
- 线程发生异常后,后续代码不会执行
- 一个线程发生异常,不会影响到其它线程的执行
- 无法简单的通过 try catch 线程来捕获线程的异常
虽然,我们可以通过 try catch 代码块来捕获异常,但是我们不太可能对所有的代码块进行 try catch 操作,而且,在团队协作中,也很难要求所有人都能够按需捕获异常,所有,我们先来看看捕获异常有哪几种方式,然后按需地采取相应地解决方案。
setUncaughtExceptionHandler
在创建异常的时候,设置 UncaughtExceptionHandler
,等到该线程发生异常的时候,就会调用该 UncaughtExceptionHandler
的 uncaughtException
方法:
val thread = Thread {
throw Exception("我是测试异常")
}
thread.setUncaughtExceptionHandler { thread, throwable ->
println("发生异常的线程名字${thread.name}, 异常:${throwable.message}")
}
thread.start()
输出结果:
发生异常的线程名字Thread-0, 异常:我是测试异常
该方式适合于对单个线程进行控制,若要对线程组进行控制的时候,就需要使用到 ThreadGroup
。
ThreadGroup
使用步骤基本如下:
- 创建
ThreadGroup
的继承类,并重写uncaughtException
的方法 - 在后续创建线程的时候,传入
ThreadGroup
的实例,表示给线程为该线程组 - 后续线程发生异常,就会回调到的
ThreadGroup
的uncaughtException
方法中
具体代码如下:
val threadGroup = object : ThreadGroup("线程组1") {
override fun uncaughtException(t: Thread, e: Throwable) {
super.uncaughtException(t, e)
println("发生异常的线程组:${name} 线程名字${t.name}, 异常:${e.message}")
}
}
val thread = Thread (threadGroup){
throw Exception("我是测试异常")
}
thread.start()
运行结果:
此时可以看出,该线程是已经抛出了异常,然后被线程组进行捕获,然后进行处理,所以,这个跟 setUncaughtExceptionHandler
是不太一样的,setUncaughtExceptionHandler
是直接对线程进行捕获处理了,不会再向上层进行抛出异常,所以,当 setUncaughtExceptionHandler
和 ThreadGroup
配合使用的时候,我们需要注意下这个特点。
在这里,我给示例来方便大家理解,如何正确处理 setUncaughtExceptionHandler
和 ThreadGroup
的交互。
val threadGroup = object : ThreadGroup("线程组1") {
override fun uncaughtException(t: Thread, e: Throwable) {
super.uncaughtException(t, e)
println("发生异常的线程组:${name} 线程名字${t.name}, 异常:${e.message}")
}
}
val thread = Thread (threadGroup){
throw Exception("我是测试异常")
}
val defaultUncaughtExceptionHandler = thread.uncaughtExceptionHandler
thread.setUncaughtExceptionHandler { thread, throwable ->
if (throwable.message!!.contains("测试")){
println("发生异常的线程名字${thread.name}, 异常:${throwable.message}")
}else{
defaultUncaughtExceptionHandler.uncaughtException(thread, throwable)
}
}
thread.start()
重点逻辑:
- 先保存 Thread 默认的
uncaughtExceptionHandler
,再设置setUncaughtExceptionHandler
- 当线程发生异常的时候,依据情况分别处理:
- 不向上抛异常,就无需额外代码
- 向上抛出异常,则需要调用
defaultUncaughtExceptionHandler.uncaughtException(thread, throwable)
下面我们来看看具体的运行结果:
throw Exception("我是测试异常")
的情况:
throw Exception("我是异常")
的情况:
写到这里,可能有人就有疑问了,使用 ThreadGroup
也太麻烦了吧,每次创建线程都需要传入 ThreadGroup
,有没有一种方式,直接给全部线程直接设置异常捕获。
这当然也是有的,setDefaultUncaughtExceptionHandler
就是一种解决方案。
setDefaultUncaughtExceptionHandler
在最开始的时候,我们通过 setUncaughtExceptionHandler
给单独线程设置异常捕获,而其实,每个线程都有一个默认的 DefaultUncaughtExceptionHandler
,我们重新设置默认的DefaultUncaughtExceptionHandler
就可以统一捕获线程中的异常了。
基本操作如下:
Thread.setDefaultUncaughtExceptionHandler{ thread, throwable ->
println("发生异常的线程名字${thread.name}, 异常:${throwable.message}")
}
val thread = Thread {
throw Exception("我是测试异常")
}
thread.start()
输出结果:
这个看起来也很简单,但是有两个要特别注意的点。
第一点:假如你是 Android 开发者的话,你有可能会通过这种方式去捕获 Android 全局异常,然后进行日志写入,然后杀死进程,重新启动 App。
但是,通过我们的测试发现,其它子线程发生异常也会进入这个异常捕获,这时就要判断好是不是主线程发生异常的,总不能因为子线程发生异常就把整个 App 重启了吧,没判断好,那就是妥妥地负优化了。😂
第二点:setDefaultUncaughtExceptionHandler
和 setUncaughtExceptionHandler
共同使用是有可能产生冲突的,就像 setUncaughtExceptionHandler
和 ThreadGroup
共同使用那样,所以,必要时,我们需要解决这种冲突。
由于处理逻辑跟 setUncaughtExceptionHandler
和 ThreadGroup
兼容逻辑差不多,所以这里仅提供示例,就不赘述了。
val newUncaughtExceptionHandler = Thread.UncaughtExceptionHandler { t, e ->
println("全局捕获:发生异常的线程名字${t.name}, 异常:${e.message}")
}
Thread.setDefaultUncaughtExceptionHandler(newUncaughtExceptionHandler)
val thread = Thread {
throw Exception("我是测试异常")
}
thread.setUncaughtExceptionHandler { thread, throwable ->
if (throwable.message!!.contains("测试")){
println("子线程捕获:发生异常的线程名字${thread.name}, 异常:${throwable.message}")
}else{
newUncaughtExceptionHandler.uncaughtException(thread, throwable)
}
}
thread.start()
有兴趣的同学可以自行解决下 DefaultUncaughtExceptionHandler
、newUncaughtExceptionHandler
、setUncaughtExceptionHandler
和 ThreadGroup
共同存在时的异常处理,了解下如何层层把异常上报上去。
以上的异常捕获都是基于 Thread
的,但是,由于 Thread
没有返回值,我们有时会用到 FetureTask
,那下面我们就来了解下,有没有其它方式捕获 FetureTask
的异常。
FetureTask 的异常捕获
首先,我们先写下示例代码来让 FetureTask
抛出异常:
val futureTask = FutureTask {
println("futureTask 正在执行")
throw Exception("我是测试异常")
return@FutureTask "1"
}
Thread(futureTask).start()
运行结果:
说好的异常怎么不见了???
怎么被吃了???(好吧,先给自己埋个坑,有空补充下 FutureTask 是如何吃掉异常的)
我们尝试下调用 FetureTask 的 get()
方法:
val futureTask = FutureTask {
println("futureTask 正在执行")
throw Exception("我是测试异常")
return@FutureTask "1"
}
Thread(futureTask).start()
futureTask.get()
运行结果:
异常终于出来了!
所以,对于 FutureTask 的异常操作,除了以上操作,还多了一种方式来进行捕获异常:
try {
futureTask.get()
}catch (e: Exception){
println("已捕获异常:${e.message}")
}
为什么说是除了以上操作?是因为 FutureTask 其实只是一个 Runnable 接口的实现类,具体的线程开启还是交给 Thread。
Executors 与 setUncaughtExceptionHandler、ThreadGroup
终于到线程池了!!!
其实,Executors 并没有 setUncaughtExceptionHandler 方法,我这里想表述的是:
- Thread 可以通过 setUncaughtExceptionHandler 和 ThreadGroup 来进行异常捕获
- Executors 要执行任务的时候也是要创建线程的,那么,我们可以在创建线程的时候,可以设置setUncaughtExceptionHandler 和 ThreadGroup 来捕获异常。
基本示例如下:(由于逻辑都是大同小异,就不过多说明)
val executorsService = Executors.newSingleThreadExecutor(object : ThreadFactory {
override fun newThread(r: Runnable?): Thread {
val thread = Thread(r)
thread.setUncaughtExceptionHandler(object : Thread.UncaughtExceptionHandler {
override fun uncaughtException(t: Thread, e: Throwable) {
println("发生异常的线程名字:${t.name}, 异常:${e.message}")
}
})
return thread
}
})
executorsService.execute{
throw Exception("测试异常")
}
输出结果:
不过这里有一点需要注意的是,这里是调用 execute 去提交 Runnable 的,但是,假如你使用的是 submit 去提交 Runnable 的话,异常也是会被吃掉的,原理其实就是跟 FutureTask 吃掉异常的原因一样,所以,对于这种情况,可以通过 try catch get()
去捕获异常:
try {
future.get()
}catch (e: Exception){
println("已捕获异常:${e.message}")
}
ThreadPoolExecutor 的 afterExecute
这种方式其实并不是捕获异常,而是对异常进行监听。
具体的原理为,ThreadPoolExecutor 在执行任务的时候,会调用 runWorker(Worker w)
,而 runWorker 大致代码逻辑:
final void runWorker(Worker w) {
...
Runnable task = w.firstTask;
...
while (task != null || (task = getTask()) != null) {
...
try {
...
Throwable thrown = null;
try {
task.run();
} catch (RuntimeException x) {
thrown = x;
throw x;
} catch (Error x) {
thrown = x;
throw x;
} catch (Throwable x) {
thrown = x;
throw new Error(x);
} finally {
afterExecute(task, thrown);
}
} finally {
...
}
}
...
}
其实,就是对于 run()
进行 try catch 操作,然后把异常存储到 thrown 中,最后在 finally 中执行 afterExecute(task, thrown)
。
所以,这种方式并不能去捕获异常,但是,当发生异常后,可以在 afterExecute 中获取得到异常信息,并进行自定义处理。
具体的示例代码如下:
val threadPoolExecutor = object : ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS,
LinkedBlockingQueue()) {
override fun afterExecute(r: Runnable?, t: Throwable?) {
super.afterExecute(r, t)
println("异常:${t?.message}")
}
}
threadPoolExecutor.execute {
throw Exception("测试异常")
}
运行结果: