Java如何捕获线程的执行异常

培训教学 潘老师 6个月前 (11-10) 150 ℃ (0) 扫码查看

Java线程在运行过程中,如果出现线程异常,该怎么捕获解决呢?本文就重点聊聊Java如何捕获线程的执行异常。

一般的思路就是在每个线程内部run()方法内通过try catch捕获当前线程发生的异常,不过这样有个缺点就是每个线程都需要编写重复的try catch 代码,那么我们该怎么办呢?

Java API中为我们提供了一个用于捕获线程内部运行时异常的接口UncaughtExceptionHandler ,通过实现这个接口并给线程指定异常捕获器就可以实现捕获线程中的运行时异常。

设置线程异常回调接口

在线程的run 方法中,如果有受检异常必须进行捕获处理,如果想要获得run() 方法中出现的运行时异常信息, 可以通过回调UncaughtExceptionHandler 接口获得哪个线程出现了运行时异常。

在Thread 类中有关处理运行异常的方法有:

  • getDefaultUncaughtExceptionHandler() :获得全局的( 默认的)UncaughtExceptionHandler
  • getUncaughtExceptionHandler(): 获得当前线程的UncaughtExceptionHandler
  • setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh) :设置全局的UncaughtExceptionHandler
  • setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh) :设置当前线程的UncaughtExceptionHandler

当线程运行过程中出现异常,JVM 会调用Thread 类的dispatchUncaughtException(Throwable e) 方法, 该方法会调getUncaughtExceptionHandler().uncaughtException(this, e); 如果想要获得线程中出现异常的信息, 就需要设置线程的UncaughtExceptionHandler 回调接口的uncaughtException 方法。

如果当前线程没有设定UncaughtExceptionHandler,则会调用线程组的,如果线程组也没有设定,则直接把异常的栈信息定向到System.err中。

以下介绍几种设置线程异常回调接口的情形:

自定义线程异常处理器

我们先准备一个自定义线程异常处理器:

public class CustomThreadUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
 
    private static final Logger LOGGER = LoggerFactory.getLogger(CustomThreadUncaughtExceptionHandler.class);
 
    @Override
    public void uncaughtException(Thread t, Throwable e) {
        LOGGER.error("捕获到线程发生的异常,线程信息:[{}]", JSON.toJSONString(t), e);
    }
}

全局配置

使用:Thread.setDefaultUncaughtExceptionHandler(new CustomThreadUncaughtExceptionHandler());实现全部配置。

通过调用Thread的静态方法setDefaultUncaughtExceptionHandler(),设置Thread的静态属性defaultUncaughtExceptionHandler,为我们自定义的异常处理器。

@Slf4j
public class ExceptionInChildThread implements Runnable {
 
    @Override
    public void run() {
        throw new RuntimeException("子线程发生了异常...");
    }
 
    /**
     * 模拟子线程发生异常
     *
     * @throws InterruptedException
     */
    private static void exceptionThread() throws InterruptedException {
        new Thread(new ExceptionInChildThread()).start();
        TimeUnit.MILLISECONDS.sleep(200L);
        new Thread(new ExceptionInChildThread()).start();
        TimeUnit.MILLISECONDS.sleep(200L);
        new Thread(new ExceptionInChildThread()).start();
        TimeUnit.MILLISECONDS.sleep(200L);
        new Thread(new ExceptionInChildThread()).start();
        TimeUnit.MILLISECONDS.sleep(200L);
    }
 
    public static void main(String[] args) throws InterruptedException {
        //设置全局的线程异常处理器
        Thread.setDefaultUncaughtExceptionHandler(new CustomThreadUncaughtExceptionHandler());
        exceptionThread();
    }
}

为指定线程设置特定的异常处理器

细心的同学已经发现了,Thread类还有一个实例属性private volatile UncaughtExceptionHandler uncaughtExceptionHandler; 通过给这个属性赋值,可以实现为每个线程对象设置不同的异常处理器。

测试使用:


@Slf4j
public class ExceptionInChildThread implements Runnable {
 
    @Override
    public void run() {
        throw new RuntimeException("子线程发生了异常...");
    }
 
    /**
     * 模拟子线程发生异常
     *
     * @throws InterruptedException
     */
    private static void exceptionThread() throws InterruptedException {
        Thread thread1 = new Thread(new ExceptionInChildThread());
        //为指定线程设置特定的异常处理器
        thread1.setUncaughtExceptionHandler(new CustomThreadUncaughtExceptionHandler());
        thread1.start();
        TimeUnit.MILLISECONDS.sleep(200L);
 
        new Thread(new ExceptionInChildThread()).start();
        TimeUnit.MILLISECONDS.sleep(200L);
 
        new Thread(new ExceptionInChildThread()).start();
        TimeUnit.MILLISECONDS.sleep(200L);
 
        new Thread(new ExceptionInChildThread()).start();
        TimeUnit.MILLISECONDS.sleep(200L);
    }
 
    public static void main(String[] args) throws InterruptedException {
        exceptionThread();
    }
}

线程组

@Slf4j
public class ExceptionInThreadGroup implements Runnable {
 
    @Override
    public void run() {
        throw new RuntimeException("线程任务发生了异常");
    }
 
    public static void main(String[] args) throws InterruptedException {
        ThreadGroup threadGroup = new ThreadGroup("只知道抛出异常的线程组...") {
            @Override
            public void uncaughtException(Thread t, Throwable e) {
                super.uncaughtException(t, e);
                log.error("线程组内捕获到线程[{},{}]异常", t.getId(), t.getName(), e);
            }
        };
        ExceptionInThreadGroup exceptionInThreadGroup = new ExceptionInThreadGroup();
 
        new Thread(threadGroup, exceptionInThreadGroup, "线程1").start();
        TimeUnit.MILLISECONDS.sleep(300L);
 
        //优先获取绑定在thread对象上的异常处理器
        Thread thread = new Thread(threadGroup, exceptionInThreadGroup, "线程2");
        thread.setUncaughtExceptionHandler(new CustomThreadUncaughtExceptionHandler());
        thread.start();
        TimeUnit.MILLISECONDS.sleep(300L);
 
        new Thread(threadGroup, exceptionInThreadGroup, "线程3").start();
    }
}

线程池

public class CatchThreadPoolException {
    public static void main(String[] args) {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                2,
                4,
                1L,
                TimeUnit.MINUTES,
                new LinkedBlockingDeque<>(1024),
                new ThreadFactory() {
                    @Override
                    public Thread newThread(Runnable r) {
                        Thread thread = new Thread(r);
                        //设置线程异常处理器
                        thread.setUncaughtExceptionHandler(new CustomThreadUncaughtExceptionHandler());
                        return thread;
                    }
                }
        );
 
        threadPoolExecutor.execute(new Runnable() {
                                       @Override
                                       public void run() {
                                           throw new RuntimeException("execute()发生异常");
                                       }
                                   }
        );
 
        threadPoolExecutor.submit(new Runnable() {
            @Override
            public void run() {
                throw new RuntimeException("submit.run()发生异常");
            }
        });
 
        threadPoolExecutor.submit(new Callable<String>() {
            @Override
            public String call() throws Exception {
                throw new RuntimeException("submit.call()发生异常");
            }
        });
      threadPoolExecutor.shutdown();
    }
}

结果: 并不符合预期,预期应该捕获三个异常

  • 只捕获到了通过execute()提交的任务的异常
  • 没有捕获到通过submit()提交的任务的异常

通过afterExecute()捕获submit()任务的异常

  • 通过submit()方法的源码可以发现,submit()是将runnable()封装成了RunnableFuture<Void>,并最终调用execute(ftask);执行。
@Slf4j
public class CatchThreadPoolException {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                2,
                4,
                1L,
                TimeUnit.MINUTES,
                new LinkedBlockingDeque<>(1024),
                new ThreadFactory() {
                    @Override
                    public Thread newThread(Runnable r) {
                        Thread thread = new Thread(r);
                        //设置线程异常处理器
                        thread.setUncaughtExceptionHandler(new CustomThreadUncaughtExceptionHandler());
                        return thread;
                    }
                }
        ) {
            /**
             * 捕获{@code FutureTask<?>}抛出的异常
             *
             * @param r
             * @param t
             */
            @Override
            protected void afterExecute(Runnable r, Throwable t) {
                super.afterExecute(r, t);
                if (r instanceof FutureTask<?>) {
                    try {
                        //get()的时候会将异常内的异常抛出
                        ((FutureTask<?>) r).get();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                        Thread.currentThread().interrupt();
                    } catch (ExecutionException e) {
                        log.error("捕获到线程的异常返回值", e);
                    }
                }
                //Throwable t永远为null,拿不到异常信息
                //log.error("afterExecute中捕获到异常,", t);
            }
        };
 
        threadPoolExecutor.execute(new Runnable() {
           @Override
           public void run() {
               throw new RuntimeException("execute()发生异常");
           }
       }
        );
        TimeUnit.MILLISECONDS.sleep(200L);
 
        threadPoolExecutor.submit(new Runnable() {
            @Override
            public void run() {
                throw new RuntimeException("submit.run()发生异常");
            }
        });
        TimeUnit.MILLISECONDS.sleep(200L);
 
        threadPoolExecutor.submit(new Callable<String>() {
            @Override
            public String call() throws Exception {
                throw new RuntimeException("submit.call()发生异常");
            }
        }).get();   //get()的时候会将异常抛出
 
        threadPoolExecutor.shutdown();
    }
}

getStackTrace()方法

Thread还提供了一个Thread.currentThread().getStackTrace()方法,返回一个表示该线程堆栈转储的堆栈跟踪元素数组。

如果该线程尚未启动或已经终止,则该方法将返回一个零长度数组。如果返回的数组不是零长度的,则其第一个元素代表堆栈顶,它是该序列中最新的方法调用。最后一个元素代表堆栈底,是该序列中最旧的方法调用。

public class UncaughtException {

 public static void main(String[] args) {
 test();
 }

 public static void test() {

 StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();

 Arrays.asList(stackTrace).stream()
 .forEach(e-> {
 Optional.of("classname:" + e.getClassName()
 + "\n methoname:" + e.getMethodName()
 + "\n linenumber:" + e.getLineNumber()
 + "\n filename:" + e.getFileName())
 .ifPresent(System.out::println);
 });
 }

}

**********************************************************************
classname:java.lang.Thread
 methoname:getStackTrace
 linenumber:1556
 filename:Thread.java
classname:uncaughtex.UncaughtException
 methoname:test
 linenumber:34
 filename:UncaughtException.java
classname:uncaughtex.UncaughtException
 methoname:main
 linenumber:29
 filename:UncaughtException.java

 总结

以上就是Java如何捕获线程的执行异常的全部内容。


版权声明:本站文章,如无说明,均为本站原创,转载请注明文章来源。如有侵权,请联系博主删除。
本文链接:https://www.panziye.com/teach/10969.html
喜欢 (0)
请潘老师喝杯Coffee吧!】
分享 (0)
用户头像
发表我的评论
取消评论
表情 贴图 签到 代码

Hi,您需要填写昵称和邮箱!

  • 昵称【必填】
  • 邮箱【必填】
  • 网址【可选】