我们都知道当程序运行出错时 , 会出现异常 , 并有异常信息输出 , 且如果不对异常进行捕获处理的话 , 是会影响到代码的执行的 .
但是如果在线程中出现异常, 则不然, 线程中出现的异常是没办法进行上抛捕获的 , 一般大家遇到这种情况都是在线程内进行异常捕获处理 , 但如果我现在需要将异常抛出, 告知主进程呢 ?
首先我们需要知道 , 异常分为两类 ,分别是编译时异常和运行时异常 ,
定义方法时,必须声明所有可能抛出的异常。在调用该方法时,必须手动捕获它的checked Exception,即受检异常。如IO异常,SQL异常等。因此:编译时实际可以理解为需要被手动捕获的异常,实际就是编译器会主动检查的异常,如B方法中主动抛出了异常,若方法A中调用B方法,也需要手动捕获该异常,当没有手动捕获异常时,编译器会报错
运行时异常(RuntimeException),在定义方法时是不需要声明的,如空指针异常。在调用该方法时也不需要手动捕获该异常,也就是说,运行时异常也可以理解为未捕获的异常,运行过程中如果发生了此类异常,会被直接抛给jvm,由jvm来处理该异常
线程设计的理念是自己线程内部的异常由自己线程内部来处理,因此假设在main主线程中创建了一个子线程,如果子线程出现了异常,main主线程是无法感知和catch到的(即使子线程出现了异常,父线程不会有任何影响,依然会正常运行)
请看以下示例代码 , 我们在main函数里面new 一个线程 , 然后手动制造一个运行时异常
- public class Thread03 {
-
- public static void main(String[] args) {
- try{
- Thread thread = new Thread(() -> {
- int i = 10/0;
- });
- thread.start();
- }catch (Exception e){
- System.out.println("异常了......");
- }
-
- }
- }
分母不能为0 , 运行后报错如下 , 因为异常无法抛出去,所以异常由JVM接管,我们手动无法捕获。
我们通过查看Thread的源码 , 可以看到如下代码 , UncaughtExceptionHandler , 当线程由于未捕获的异常而突然终止时调用的处理程序的接口。
- public interface UncaughtExceptionHandler {
- /**
- * Method invoked when the given thread terminates due to the
- * given uncaught exception.
- * <p>Any exception thrown by this method will be ignored by the
- * Java Virtual Machine.
- * @param t the thread
- * @param e the exception
- */
- void uncaughtException(Thread t, Throwable e);
- }
-
-
- // 向处理程序调度未捕获的异常。此方法旨在仅由JVM调用。
- private void dispatchUncaughtException(Throwable e) {
- getUncaughtExceptionHandler().uncaughtException(this, e);
- }
注意 ,在这里 , 线程内的一个异常处理顺序 , 当线程因未捕获异常即将终止时,Java虚拟机将使用getUncaughtExceptionHandler查询线程的未捕获异常处理程序,并调用处理程序的uncaughtException方法,将线程和异常作为参数传递。如果线程未显式设置其UncaughtExceptionHandler,则其ThreadGroup对象将充当其UncaughtExceptionHandler。如果ThreadGroup对象没有处理异常的特殊要求,它可以将调用转发给默认的未捕获异常处理程序。
然后我们通过下图的位置查看ThreadGroup源码 , 可以发现ThreadGroup自身就是一个handler,查看ThreadGroup的源码就可以发现,ThreadGroup实现了Thread.UncaughtExceptionHandler接口,并实现了默认的处理方法。默认的未捕获异常处理器处理时,会调用 System.err 进行输出,也就是直接打印到控制台了。
由此可知,最终JVM是调用未捕获的异常处理器的uncaughtException()方法来处理异常的,并且是直接打印到控制台。
前面在说线程内异常处理方式时候 , 说到了线程内异常的处理方式是通过设置线程的异常回调接口UncaughtExceptionHandler(实际处理该线程异常的仍然是该子线程,只是通过回调接口,把处理异常的方法从具体的可执行对象本身的run方法中抽离了出去,使用这种方法,可以监控哪个子线程发生了异常,因为每一个线程发生异常都会回调该接口)
线程发生异常时 , JVM会首先调用Thread类的dispatchUncaughtException,该方法调用getUncaughtExceptionHandler().uncaughtException(this,e , getUncaughtExceptionHandler()方法中的执行逻辑是 -> 若当前线程的uncaughtExceptionHandler不为空,则使用当前线程的uncaughtExceptionHandler,否则使用线程组的uncaughtExceptionHandler
根据该回调接口可知 , 如果想要获得线程的运行时异常,就需要设置当前线程的uncaughtExceptionHandler实例,并实现该回调接口的uncaughtException(this,e)方法
定义一个线程异常处理器
- public class MyThreadExceptionHandler implements Thread.UncaughtExceptionHandler {
- /**
- * Method invoked when the given thread terminates due to the
- * given uncaught exception.
- * <p>Any exception thrown by this method will be ignored by the
- * Java Virtual Machine.
- *
- * @param t the thread
- * @param e the exception
- */
- @Override
- public void uncaughtException(Thread t, Throwable e) {
- System.out.println("自定义异常处理逻辑......");
- }
- }
set 自定义异常处理器
- public class Thread03 {
-
- public static void main(String[] args) {
- try{
- Thread thread = new Thread(() -> {
- int i = 10/0;
- });
- // set 自定义异常处理器
- thread.setUncaughtExceptionHandler(new MyThreadExceptionHandler());
- thread.start();
- }catch (Exception e){
- System.out.println("异常了......");
- }
-
- }
- }
我们通过给Thread设置自定义的异常捕获处理器,覆盖jvm默认的异常处理器,就可以实现异常的自定义处理。