多线程捕获线程中的异常
1、Thread类最佳实践:
写的时候最好要设置线程名称 Thread.name,并设置线程组 ThreadGroup,目的是方便管理。在出现问题的时候,打印线程栈 (jstack -pid) 一眼就可以看出是哪个线程出的问题,这个线程是干什么的。
2、二种实现方式
方法一 Thread方式通过线程组,线程名,并设置UncaughtExceptionHandler来捕获异常
package com.hmblogs.backend.test;
public class ThreadExceptionTest {
public static void main(String[] args) {
try{
Thread t =new Thread(new Runnable(){
@Override
public void run() {
int i = 10/0;
System.out.println("run....");
}
});
t.setUncaughtExceptionHandler(
new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("catch 到了");
}
});
t.start();
}catch(Exception e){
System.out.println("catch 不到");
}
}
}
执行结果截图如下:

然后尝试不加Handler的情况,能不能捕获到异常呢
package com.hmblogs.backend.test;
public class ThreadExceptionTest {
public static void main(String[] args) {
try{
Thread t =new Thread(new Runnable(){
@Override
public void run() {
int i = 10/0;
System.out.println("run....");
}
});
t.start();
}catch(Exception e){
System.out.println("catch 不到");
}
}
}
执行结果截图如下:

即使不加Handler 也catch不到,说明线程不能用try,catch来获取线程中的异常。需要使用Handler。
方法二 使用ExecutorService来捕获线程
- 由于线程的本质特性,使得你不能捕获从线程中逃逸的异常。一旦异常逃出任务的run()方法它就会向外传播到控制台,除非你采取特殊的步骤捕获这种错误的异常。
- 在Java SE5之前,你可以使用线程组来捕捉这种异常,但是有了Java SE5,就可以用Executor来解决这个问题了。
- 下面的任务总是会抛出一个异常,该异常会传播到其run()方法的外部,并且main()展示了当你运行它时所发生的事情:
package com.hmblogs.backend.test;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ExceptionThread implements Runnable {
public void run() {
throw new RuntimeException();
}
public static void main(String[] args) {
ExecutorService service = Executors.newCachedThreadPool();
service.execute(new ExceptionThread());
}
}
输出如下:

将main的主体放在try-catch语句块中也是没有作用的:
package com.hmblogs.backend.test;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ExceptionThread implements Runnable {
public void run() {
System.out.println("执行了线程");
throw new RuntimeException();
}
public static void main(String[] args) {
try {
ExecutorService service = Executors.newCachedThreadPool();
service.execute(new ExceptionThread());
} catch (RuntimeException e) {
System.out.println("Catched Runtime Exception.");
}
}
}
执行结果如下:

这将产生于前面示例相同的结果:未捕获的异常。
- 为了解决这个问题,我们要修改Executor产生线程的方式。Thread.UncaughtExceptionHandler是Java SE5中的新接口,它允许你在每个Thread对象上都附着一个异常处理器。
- Thread.UncaughtExceptionHandler.uncaughtException()会在线程因未捕获的异常而临近死亡时被调用。为了使用它,我们创建了一个新类型的ThreadFactory,它将在每个新创建的Thread对象上附着一个Thread.UncaughtExceptionHandler。
package com.hmblogs.backend.test;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
public class ExceptionThread2 implements Runnable {
public void run() {
throw new RuntimeException("NullPointer");
}
public static void main(String[] args) {
ThreadFactory tFactory = new MyThreadFactory();
ExecutorService service = Executors.newCachedThreadPool(tFactory);
Runnable task = new ExceptionThread2();
service.execute(task);
}
}
class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
// 处理从线程里抛出来的异常。
public void uncaughtException(Thread t, Throwable e) {
System.out.println("Catched Throwable: " +
e.getClass().getSimpleName() + ", " + e.getMessage());
}
}
class MyThreadFactory implements ThreadFactory {
// 重新组织创建线程的方式
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
// 为每一个线程都绑定一个异常处理器。
t.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
System.out.println("Thread[" + t.getName() + "] created.");
return t;
}
}
执行结果截图如下:

可以看到,线程池中有2个线程,当一个线程发生异常时,该异常被捕捉了。
上面的示例使得你可以按照具体情况(在newThread()方法中使用if, case等语句)为每个线程逐个的设置处理器。如果你知道将要在代码中处处使用相同的异常处理器,那么更简单的方式是在Thread类中设置一个静态域,并将这个处理器设置为默认的处理器即可:
package com.hmblogs.backend.test;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ExceptionThread2 implements Runnable {
public void run() {
throw new RuntimeException("NullPointer");
}
public static void main(String[] args) {
// 为线程设置默认的异常处理器。
Thread.setDefaultUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
ExecutorService exec = Executors.newCachedThreadPool();
exec.execute(new ExceptionThread2());
}
}
class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
// 处理从线程里抛出来的异常。
public void uncaughtException(Thread t, Throwable e) {
System.out.println("Catched Throwable: " +
e.getClass().getSimpleName() + ", " + e.getMessage());
}
}
执行结果如下:

这个处理器只有在不存在线程专有的未捕获异常处理器的情况下才会被调用。系统会检查线程专有版本,如果没有发现,则检查线程组是否有专有的uncaughtException()方法,如果也没有,才会调用defaultUncaughtExceptionHandler.
扩展:
如何在多个子线程中处理异常并返回?
现在让我们来解决面试题提出的问题:在多个子线程中,其中一个子线程异常时,主线程如何抛出异常并返回。
为了实现这个需求,我们可以借助ExecutorService来管理多个子线程,并使用Future来获取每个子线程的执行结果。ExecutorService是Java提供的线程池框架,可以用来管理和执行多个线程。
首先,我们创建一个线程池,并提交多个子线程任务,然后使用Future来获取每个子线程的执行结果。接着,我们可以在主线程中遍历Future,检查每个子线程的执行结果,如果其中一个子线程发生异常,我们可以在主线程中抛出异常并返回。
package com.hmblogs.backend.test;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
public class Test{
public static void main(String[] args)throws Exception {
ExecutorService executorService= Executors.newFixedThreadPool( 5);
List<Future<Integer>> futures =new ArrayList<>();
for(int i=0;i<5;i++){
int finalI = i;
Callable<Integer> task = new Callable<Integer>(){
@Override
public Integer call() throws Exception{
// 子线程的任务代码
if(finalI== 4){
throw new Exception("子线程异常");
}
return finalI;
}
};
Future<Integer> futureTemp = executorService.submit(task);
futures.add(futureTemp);
}
for(Future<Integer> future:futures){
try {
Integer result=future.get();
// 处理子线程的执行结果
System.out.println("子线程执行结果:"+result);
}catch(ExecutionException e){
// 子线程抛出异常
Throwable cause=e.getCause();
if(cause instanceof Exception) {
throw (Exception) cause;
}
}
}
executorService.shutdown();
}
}
在这个例子中,我们创建了一个包含5个子线程的线程池,每个子线程的任务中模拟了一个异常情况。在主线程中,我们使用Future来获取每个子线程的执行结果,如果有子线程发生异常,我们会在主线程中抛出异常。执行结果如下图所示:

异常处理的注意事项
在处理多线程中的异常时,有一些需要注意的事项:
-
在子线程中捕获异常并进行处理,以避免异常传播到其他线程。
-
使用UncaughtExceptionHandler接口来处理未捕获的异常,以确保在主线程中也能捕获子线程的异常。
-
使用ExecutorService来管理多个子线程,可以更方便地获取子线程的执行结果。
-
在主线程中使用Future来获取子线程的执行结果,并检查是否有异常发生。
-
注意异常的处理顺序,确保能够正确地捕获并处理异常。
总结
在Java多线程编程中,处理多个子线程中的异常并返回是一个常见的问题。通过使用ExecutorService和Future,我们可以方便地管理多个子线程,并在主线程中捕获子线程的异常。这样可以确保程序能够在发生异常时做出合适的处理,而不会导致程序崩溃或产生不可预测的结果。

被折叠的 条评论
为什么被折叠?



