一、一个线程在执行过程中发生了异常会怎样?
那要看我们是否对这个异常进行了处理,如果处理了,那么线程会继续执行,如果没有处理,那么线程会释放掉自己所持有的锁,退出执行,如果这个线程是主线程,那么主线程退出执行了,程序也会停止执行,如果这个线程不是主线程,那么它的退出不会影响到主线程和其他线程,程序会继续执行。但无论这个线程是否是主线程,线程因异常而退出会导致我们的业务执行失败,会影响正常的业务功能,所以我们应该在开发中用合适的方式去处理这些异常。
java中的异常分为检查时异常和运行时异常两大类,java要求我们必须显示地处理检查时异常,如果不处理,会在编译期报错,而对于运行时异常我们可以处理,也可以不处理,都是可以通过编译的,只是,如果不处理,那么可能在程序的运行期间因为发生RuntimeExeception而导致线程异常退出。那么对于运行时异常我们应该如何处理呢?
首先,在单线程环境中,我们可以通过try/catch语句块去进行异常的捕获处理,或者是用throw/throws方式将异常向上抛出,而对于抛出的异常,可以由外层的调用方法来进行捕获处理。那么多线程执行过程中发生的异常如何处理呢?
二、处理多线程执行过程中发生的异常
1、Runnable类型任务的异常处理
因为Runnable的run方法是无法将异常向上抛出的,所以它在执行过程中如果发生了异常,这个异常要么在run方法内部处理,要么由任务线程的UncaughtExceptionHandler来处理,如果我们既没有在run方法内部捕获它,又没为任务线程设置UncaughtExceptionHandler,那么这个异常的发生将导致任务线程异常退出,任务执行失败。
未处理异常的情况:
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("模拟Runnable异常...");
int num = 1/0;
}
});
thread.start();
}
在run方法内部处理异常:
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("模拟Runnable异常...");
try {
int num = 1/0;
}catch (Exception e){
System.out.println("Runnable任务内部处理异常");
}
}
});
thread.start();
}
用UncaughtExceptionHandler处理未捕获异常:
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("模拟Runnable异常...");
int num = 1/0;
}
});
Thread.UncaughtExceptionHandler uncaughtExceptionHandler = new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("处理线程" + t.getName() + "执行过程中的未捕获异常:" + e.getMessage());
}
};
thread.setUncaughtExceptionHandler(uncaughtExceptionHandler);
thread.start();
}
UncaughtExceptionHandler
UncaughtExceptionHandler是定义在Thread类中的一个内部接口,它是线程的未捕获异常处理器,它的作用是处理线程执行过程中发生的未捕获异常,当线程在执行过程中发生了异常时,jvm会先去查找当前方法有没有对这个异常做捕获处理,也就是查找当前方法的catch语句块,如果没做捕获处理,则查找当前方法是否声明抛出了这个异常,如果声明抛出这一类异常了,则会沿着方法调用栈往上查找,如果调用栈中各个方法均声明抛出了这一类异常,而一直查找到了栈底却依然没有对于这个异常的捕获处理,则会去查找此线程有没有设置UncaughtExceptionHandler,如果设置了,则由这个UncaughtExceptionHandler处理,如果没设置,则再去查找当前线程所属线程组有没有设置UncaughtExceptionHandler,如果设置了,就由它来处理,如果没设置,则该异常被定位到System.err。Thread类中还定义了一个setUncaughtExceptionHandler方法,用于为线程设置未捕获异常处理器,但是UncaughtExceptionHandler只对Runnable类型的任务线程起作用,当线程执行的是Callable类型的任务时,即便我们为任务线程设置了UncaughtExceptionHandler,它也不会生效。
2、Callable类型任务的异常处理
UncaughtExceptionHandler在Callable类型的任务线程里是不生效的:
public static void main(String[] args) {
FutureTask<Integer> futureTask = new FutureTask<>(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
System.out.println("模拟Callable异常...");
int num = 1/0;
return num;
}
});
Thread thread = new Thread(futureTask);
Thread.UncaughtExceptionHandler uncaughtExceptionHandler = new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("处理线程" + t.getName() + "执行过程中的未捕获异常:" + e.getMessage());
}
};
thread.setUncaughtExceptionHandler(uncaughtExceptionHandler);
thread.start();
}
可见,当call方法执行过程中发生了未捕获异常时,这个异常也不会被UncaughtExceptionHandler捕获到,为什么呢?我们在main线程中调用一下FutureTask对象的get方法看一下会有怎样的结果:
public static void main(String[] args) {
FutureTask<Integer> futureTask = new FutureTask<>(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
System.out.println("模拟Callable异常...");
int num = 1/0;
return num;
}
});
Thread thread = new Thread(futureTask);
Thread.UncaughtExceptionHandler uncaughtExceptionHandler = new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("处理线程" + t.getName() + "执行过程中的未捕获异常:" + e.getMessage());
}
};
thread.setUncaughtExceptionHandler(uncaughtExceptionHandler);
thread.start();
try {
futureTask.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
可以看到,任务线程中的异常被抛到了get方法的调用线程main线程这里,可见,即便是Callable任务执行过程中发生了异常,这个异常也不会在任务线程里出现,当然也就不能被任务线程的UncaughtExceptionHandler捕获到,它只会在调用FutureTask对象的get方法的线程里出现,并且它被包装成了一个ExecutionException。那么在这个过程中到底发生了什么呢?
FutureTask对于Callable任务异常的处理
Callable类型的任务线程在被调度到的时候,执行的是FutureTask对象的run方法,而在run方法的内部执行的又是Callable对象的call方法,如果call方法在执行过程中发生了异常,那么这个异常会被run方法捕获处理,FutureTask类有一个Object类型的成员变量outcome,这个成员变量用于存放call方法的执行结果或call方法在执行过程中抛出的异常,而outcome存放的异常在当前的FutureTask对象的get方法中又被重新包装成了一个ExecutionException异常抛到了get方法外部,也就是调用get方法的线程中。源码如下:
再来看一下setException方法的源码:
这就是在任务线程内部捕获不到call方法异常的原因。再看一下get方法:
所以对于Callable任务的异常,我们可以在任务内部【也就是call方法内部】捕获原发类型的异常来处理,或者在FutureTask的get方法的调用线程里捕获ExecutionException类型的异常来处理。
3、线程池中任务异常的处理
当我们使用的是线程池的execute方法来提交多线程任务时,因为它提交的是Runnable类型的任务,所以我们为任务线程设置的UncaughtExceptionHandler是可以生效的,所以我们可以实现一个ThreadFactory,再实现一个UncaughtExceptionHandler,在ThreadFactory的newThread方法内部,为每一个创建出来的线程都设置这个UncaughtExceptionHandler,让线程使用这个UncaughtExceptionHandler去处理任务执行过程中发生的未捕获异常。
public static void main(String[] args) {
Thread.UncaughtExceptionHandler uncaughtExceptionHandler = new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("处理线程" + t.getName() + "执行过程中的未捕获异常:" + e.getMessage());
}
};
ThreadFactory threadFactory = new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setUncaughtExceptionHandler(uncaughtExceptionHandler);
return thread;
}
};
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2,3,200L,TimeUnit.MILLISECONDS
,new LinkedBlockingDeque<>(2),threadFactory);
for (int i = 0; i < 5; i++) {
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "模拟Runnable异常");
int num = 1/0;
}
});
}
}
所以当我们用execute方法往线程池中提交任务时,既可以在任务内部【run方法内部】处理异常,也可以为线程设置UncaughtExceptionHandler来处理任务的未捕获异常。
但是用submit方法提交到线程池中的任务所抛出的异常却无法被任务线程的UncaughtExceptionHandler所捕获到:
比如,当提交的是Callable类型的任务时:
public static void main(String[] args) {
Thread.UncaughtExceptionHandler uncaughtExceptionHandler = new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("处理线程" + t.getName() + "执行过程中的未捕获异常:" + e.getMessage());
}
};
ThreadFactory threadFactory = new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setUncaughtExceptionHandler(uncaughtExceptionHandler);
return thread;
}
};
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2,3,200L,TimeUnit.MILLISECONDS
,new ArrayBlockingQueue<>(2),threadFactory);
for (int i = 0; i < 5; i++) {
threadPoolExecutor.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
System.out.println("模拟Callable异常");
int num = 1/0;
return num;
}
});
}
}
可见UncaughtExceptionHandler没有生效,再调用一下submit方法返回的Future对象的get方法看一下结果:
public static void main(String[] args) {
Thread.UncaughtExceptionHandler uncaughtExceptionHandler = new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("处理线程" + t.getName() + "执行过程中的未捕获异常:" + e.getMessage());
}
};
ThreadFactory threadFactory = new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setUncaughtExceptionHandler(uncaughtExceptionHandler);
return thread;
}
};
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2,3,200L,TimeUnit.MILLISECONDS
,new ArrayBlockingQueue<>(2),threadFactory);
for (int i = 0; i < 5; i++) {
Future<Integer> result = threadPoolExecutor.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
System.out.println("模拟Callable异常");
int num = 1/0;
return num;
}
});
try {
result.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
Callable任务的异常被包装为ExecutionException异常抛到了调用get方法的main线程中,原因也很简单,submit方法肯定将我们提交的Callable任务封装到了FutureTask对象中执行,而FutureTask对象的run方法对call方法抛出的异常做了捕获处理,将异常放在了自己的outcome变量中保存。
submit方法的源码也确实是这样实现的:
FutureTask.run方法源码就不贴了,上面已经贴过了。
而即便是我们用submit方法提交的是一个Runnable类型的任务,任务线程的UncaughtExceptionHandler依然不会生效:
public static void main(String[] args) {
Thread.UncaughtExceptionHandler uncaughtExceptionHandler = new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("处理线程" + t.getName() + "执行过程中的未捕获异常:" + e.getMessage());
}
};
ThreadFactory threadFactory = new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setUncaughtExceptionHandler(uncaughtExceptionHandler);
return thread;
}
};
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2,3,200L,TimeUnit.MILLISECONDS
,new ArrayBlockingQueue<>(2),threadFactory);
for (int i = 0; i < 5; i++) {
Future result = threadPoolExecutor.submit(new Runnable() {
@Override
public void run() {
System.out.println("模拟Runnable异常");
int num = 1/0;
}
});
try {
result.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
任务异常被包装为ExecutionException抛到了调用Future.get方法的main线程中,而未被任务线程的UncaughtExceptionHandler捕获到。原因是,submit方法用我们传入的Runnable对象构造了一个FutureTask对象去执行,并将Runnable对象转换为Callable类型赋给了这个FutureTask对象的callable成员变量。
源码:
因此这个异常只能在调用Future.get方法的线程中以ExecutionException类型捕获到。
总结一下,使用execute方法提交的多线程任务,异常可以在任务内部处理,也可以为任务线程设置UncaughtExceptionHandler处理。使用submit方法提交的多线程任务,异常可以在任务内部处理,也可以在调用线程中处理,也就是在调用Future对象的get方法去获取任务结果的线程中处理。
在实际的开发工作中,我们要根据业务需求去选择使用哪种方式提交多线程任务,当我们选定方式之后,还要去使用合适的策略去处理任务异常。