Java中子线程的异常处理
最近在面试的过程中被问到子线程中异常有几种处理方式,突然意识到没有了解过这方面的知识,于是通过这篇学习笔记来记录下Java中子线程的异常处理方式。
子线程异常处理方式
通过学习,了解到子线程中的异常处理方式有如下三种形式,我们依次通过案例来使用下这三种方式。
- 在子线程中捕获异常并处理
- 为线程设置"未捕获异常处理器"UncaughtExceptionHandler
- 通过Future的get方法捕获异常
在子线程中捕获异常并处理
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class ThreadException {
//实现Runnable接口
static class MyTask implements Runnable {
@Override
public void run() {
System.out.println("执行线程");
//捕捉异常
try {
int a = 3/0;
}catch(Exception e) {
System.out.println("处理异常")
}
System.out.println("线程结束");
}
}
public static void main(String[] args) {
//启动线程
new Thread(new MyTask()).start();
}
}
为线程设置"未捕获异常处理器"UncaughtExceptionHandler
设置方式
- 可以调用Thread.setUncaughtExceptionHandler()设置
- 如果使用的线程池,可以通过ThreadFactory.setUncaughtExceptionHandler()设置。
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class ThreadException {
//实现Runnable接口
static class MyTask implements Runnable {
@Override
public void run() {
int a = 3/0;
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
Thread thread = new Thread(new MyTask());
//设置UncaughtExceptionHandler,可以在这个方法中打印日志
thread.setUncaughtExceptionHandler((Thread,Throwable) -> {
System.out.println("捕捉到异常了");
});
//启动线程
thread.start();
}
}
通过Future的get方法捕获异常
注意:对于有返回值的线程来说,如果不调用Future的get方法,假如子线程没有处理异常,那么这个异常会被"吞掉",很难定位问题
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class ThreadException {
static class MyTask implements Callable {
@Override
public Object call() throws Exception {
System.out.println("执行线程");
//制造异常
int a = 3/0;
return 1;
}
}
public static void main(String[] args) {
FutureTask futureTask = new FutureTask<>(new MyTask());
new Thread(futureTask).start();
//通过get方法捕获异常
try {
futureTask.get();
}catch (Exception e) {
System.out.println("捕获到异常了");
}
}
}
子线程异常可能被"吞掉"
对于有返回值的线程,也就是实现了Callable接口的线程,子线程发生的异常可能被"吞掉",样例如下
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class ThreadException {
static class MyTask implements Callable {
@Override
public Object call() throws Exception {
System.out.println("执行线程");
//制造异常
int a = 3/0;
return 1;
}
}
public static void main(String[] args) {
FutureTask futureTask = new FutureTask<>(new MyTask());
//启动线程
new Thread(futureTask).start();
}
}
运行结果:
通过上面的运行结果我们发现,控制台什么也没有打印,但是我们子线程中有这么一行代码是会抛出异常的int a =3/0
为什么异常会被"吞掉呢"
我们写的Callable实现类要封装在FutureTask中。
FutureTask futureTask = new FutureTask<>(new MyTask());
我们可以看下FutureTask源码,我们设置的任务首先赋值给了FutureTask内部的callable,然后在去调用FutureTask中的run方法
我们继续看FutureTask的run源码,执行任务并捕获了任务中可能会出现的异常,这里并没有打印,而是通过setException(ex)方法,将抛出的异常会被放到 outcome 对象中,这个对象就是submit()方法会返回的FutureTask对象执行get()方法得到的结果。
所以对于有返回值的任务要么通过FutureTask的get方法去捕捉异常,要么在子线程中就处理掉,不然会出现任务莫名其妙的停止,并且定位不到问题的情况。
有道无术,术尚可求。
有术无道,止于术。