欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

UncaughtExceptionHandler 获取线程运行时异常

程序员文章站 2024-03-20 17:06:22
...

我们知道线程执行体的方法接口定义为:public void run(),因此线程在执行单元中是不允许抛出checked异常的,且线程之间是相对独立的,他们运行在自己的上下文当中,派生它的线程无法直接感知到它在运行时出现的异常信息。为了解决这个问题,java提供了UncaughtExceptionHandler接口,当线程在运行时发生异常时,会回调这个接口,从而得知哪个线程在运行时出错。

UncaughtExceptionHandler 接口定义如下:

UncaughtExceptionHandler 获取线程运行时异常

使用@FunctionalInterface表示只有一个抽象方法的接口。

在线程发生异常时,jvm会调用Thread类中的dispatchUncaughtException方法

UncaughtExceptionHandler 获取线程运行时异常

该方法调用了getUncaughtExceptionHandler()获取UncaughtExceptionHandler异常处理器的实例对象

UncaughtExceptionHandler 获取线程运行时异常

通过源码我们可以看出,当线程发生异常时,jvm会调用Thread类中的dispatchUncaughtException方法,会判断当前线程是否设置了getUncaughtExceptionHandler,如果设置了就执行自己的uncaughtException方法,否则就使用所在ThreadGroup的uncaughtException方法。ThreadGroup 异常处理源码如下:

    
public void uncaughtException(Thread t, Throwable e) {
        //t 发生异常的线程  e 异常信息
        //判断parent(父级ThreadGroup)是否为null
        if (parent != null) {
            //调用父级ThreadGroup的uncaughtException方法
            parent.uncaughtException(t, e);
        } else {
            //如果没有父级,则尝试获取全局的UncaughtExceptionHandler
            Thread.UncaughtExceptionHandler ueh =
                Thread.getDefaultUncaughtExceptionHandler();
            if (ueh != null) {
                //如果设置了全局的异常处理器,则调用全局的异常处理器的uncaughtException
                ueh.uncaughtException(t, e);
            } else if (!(e instanceof ThreadDeath)) {
                //否则控制台输出错误信息
                System.err.print("Exception in thread \""
                                 + t.getName() + "\" ");
                e.printStackTrace(System.err);
            }
        }
}

由ThreadGroup源码可以看出,这个方法其实啥也没做,不断得去找上级,没有上级就调用全局异常处理器,再没有就直接控制台打印了。因此,如果真要用ThreadGroup来获取异常,我们就需要继承ThreadGroup来重写这个方法了。

在这里,我们提到了全局异常处理器,全局异常处理器是在Thread类中定义的

UncaughtExceptionHandler 获取线程运行时异常

UncaughtExceptionHandler 获取线程运行时异常

现在我们编写代码来验证是否就是这样一个流程:

1、设置全局异常处理器

    public static void main(String[] args) {
        //设置接口回调
        //设置全局的异常处理器
        Thread.setDefaultUncaughtExceptionHandler((t,e)->{
            System.out.println(t.getName()+" occur exception");
            e.printStackTrace();
        });
        final Thread thread = new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(2);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            //unchecked异常
            System.out.println(1/0);
        },"Test-Thread");
        thread.start();
    }

运行结果:

UncaughtExceptionHandler 获取线程运行时异常

2、为线程添加自定义异常处理器:

        //设置接口回调
        //设置全局的异常处理器
        Thread.setDefaultUncaughtExceptionHandler((t,e)->{
            System.out.println(t.getName()+" occur exception");
            e.printStackTrace();
        });
        final Thread thread = new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(2);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            //unchecked异常
            System.out.println(1/0);
        },"Test-Thread");
        //为某个线程设计异常处理器
        thread.setUncaughtExceptionHandler((t,e)-> {
            System.out.println(t.getName()+"发生异常");
            e.printStackTrace();
        });
        thread.start();
    }

运行结果:

UncaughtExceptionHandler 获取线程运行时异常

自定义异常处理器中获取异常信息,而不是在全局异常处理器中。

3、将线程加入到group,在ThreadGroup中处理异常信息:

//继承ThreadGroup,重写uncaughtException处理异常信息
public class MyThreadGroup extends ThreadGroup {
    public MyThreadGroup(String name) {
        super(name);
    }

    public MyThreadGroup(ThreadGroup parent, String name) {
        super(parent, name);
    }
    public void uncaughtException(Thread thread, Throwable exception){
        System.out.println("ThreadGroup 捕获异常信息");
        exception.printStackTrace();
    }
}
    
    public static void main(String[] args) {
        ThreadGroup group1 = new MyThreadGroup("T1");
        //设置全局的异常处理器
        Thread.setDefaultUncaughtExceptionHandler((t,e)->{
            System.out.println(t.getName()+" occur exception");
            e.printStackTrace();
        });
        //创建线程并指定其ThreadGroup为group1
        final Thread thread = new Thread(group1,()->{
            try {
                TimeUnit.SECONDS.sleep(1);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            System.out.println(1/0);
        },"Test-Thread");
        //设置线程自定义的异常处理器
        //thread.setUncaughtExceptionHandler((t,e)-> {
        //    System.out.println(t.getName()+"发生异常");
        //    e.printStackTrace();
        //});
        thread.start();
    }

注意,上面的代码我们注释掉了线程设置自定义处理器的部分,运行结果如下:

UncaughtExceptionHandler 获取线程运行时异常

在ThreaGroup中捕获了异常信息,而不是使用全局的

解开注释,添加线程设置自定义处理器的部分,结果如下:

UncaughtExceptionHandler 获取线程运行时异常

在自定义的异常处理器中捕获异常,而不是在ThraedGroup和全局异常处理器当中。

结论:

获取线程未捕获的异常有三种方式:

1、线程设置自定义的UncaughtExceptionHandler

2、设置全局的UncaughtExceptionHandler

3、在线程所属的ThreadGroup中捕获异常

通过上面的方式,线程发生异常时,派生它的线程就能获取到它的异常信息,知道是哪个子线程发生了错误。并且这三种方式有不同的优先级:自定义>ThreadGroup>全局