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

【线程】Thread.UncaughtExceptionHandler 实战与剖析 (十八)

程序员文章站 2024-03-20 17:10:58
...

我的原则:先会用再说,内部慢慢来。
学以致用,根据场景学源码


一、前言

1.1 架构

【线程】Thread.UncaughtExceptionHandler 实战与剖析 (十八)

  • java.lang.Thread.UncaughtExceptionHandler

=== 点击查看top目录 ===

1.2 Class 架构

public class Thread implements Runnable {
 @FunctionalInterface
    public interface UncaughtExceptionHandler {
        void uncaughtException(Thread t, Throwable e);
    }
}

=== 点击查看top目录 ===

1.3 能干嘛?

  1. 捕获子线程抛出的异常

=== 点击查看top目录 ===

二、实战 demo

2.1 实战一:测试 Exception 逃逸

  • 代码
public class _01_TestMultiThreadException {
    // 现象:控制台打印出异常信息,并运行一段时间后才停止
    public static void main(String[] args) {
        // 就算把线程的执行语句放到 try-catch 块中也无济于事
        try {
            ExecutorService exec = Executors.newCachedThreadPool();
            exec.execute(() -> {
                throw new RuntimeException("自定义的一个RuntimeException");
            });
            exec.shutdown();
        } catch (Exception e) {
            System.out.println("Exception has been handled!");
        }
    }
}
  • 解析:上方的 try-catch 压根没法捕获到子线程抛出的 Exception

  • 结论:

  1. 多线程运行不能按照顺序执行过程中捕获异常的方式来处理异常, 异常会被直接抛出到控制台。
  2. 由于线程的本质,使得你不能捕获从线程中逃逸的异常。一旦异常逃逸出任务的run方法,它就会向外传播到控制台,除非你采用特殊的形式捕获这种异常。

=== 点击查看top目录 ===

2.2 实战二:自定义 UncaughtExceptionHandler

  1. 步骤一:定义“异常处理器” UncaughtExceptionHandler
class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
    /*
        Thread.UncaughtExceptionHandle.uncaughtException()
            会在线程因未捕获的异常而临近死亡时被调用
     */
    @Override
    public void uncaughtException(Thread t, Throwable e) {
        // 这里可以写 if else 处理各种各样的异常
        if(e instanceof RuntimeException){
            System.out.println("### MyUncaughtExceptionHandler catch " + e);
        }
    }
}
  1. 步骤二:定义线程工厂 ThreadFactory
  • 工厂就是用来产生线程的,并给生成的线程绑定一个异常处理器。
class MyHandlerThreadFactory implements ThreadFactory {
    @Override
    public Thread newThread(Runnable r) {
        System.out.println("MyHandlerThreadFactory creating new Thread ==== Begin ====");
        Thread thread = new Thread(r);
        System.out.println("MyHandlerThreadFactory created " + thread);
        // !!! 设定线程工程的异常处理器
        thread.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
        System.out.println("eh = " + thread.getUncaughtExceptionHandler());
        System.out.println("MyHandlerThreadFactory creating new Thread ==== End ====");
        return thread;
    }
}
  1. 步骤三:定义一个会抛出unchecked异常的线程类
class ExceptionThreadDemo implements Runnable {
    @Override
    public void run() {
        Thread t = Thread.currentThread();
        System.out.println("run by -> " + t);
        System.out.println("eh -> " + t.getUncaughtExceptionHandler());
        throw new RuntimeException("这是自定义的 RuntimeException ... ");
    }
}
  1. 步骤四:使用线程工厂创建线程池,并调用其 execute 方法
    public static void test01(){
        ExecutorService exec = Executors.newCachedThreadPool(new MyHandlerThreadFactory());
        exec.execute(new ExceptionThreadDemo());
        exec.shutdown();
    }
  • 输出:
MyHandlerThreadFactory creating new Thread ==== Begin ====
MyHandlerThreadFactory created Thread[Thread-0,5,main]
eh = indi.swaaa@qq.comb81eda8
MyHandlerThreadFactory creating new Thread ==== End ====
run by -> Thread[Thread-0,5,main]
eh -> indi.swaaa@qq.comb81eda8
### MyUncaughtExceptionHandler catch java.lang.RuntimeException: 这是自定义的 RuntimeException ... 

2.3 实战三:全局定义 UncaughtExceptionHandler

  • 如果你知道将要在代码中每个地方使用相同的异常处理器,那么更简单的方式是在Thread类中设置一个静态域,并将这个处理器设置为默认的未捕获处理器。
  • 实战三相对实战二,去掉了为每个线程专门绑定处理器的 ThreadFactory
public static void test02(){
        Thread.setDefaultUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
        ExecutorService exec = Executors.newCachedThreadPool();
        exec.execute(new ExceptionThreadDemo());
        exec.shutdown();
    }

输出:

run by -> Thread[pool-1-thread-1,5,main]
eh -> java.lang.ThreadGroup[name=main,maxpri=10]
### MyUncaughtExceptionHandler catch java.lang.RuntimeException: 这是自定义的 RuntimeException ... 

=== 点击查看top目录 ===

2.4 实战四:全局 Handler 与自定义 Handler 并存,听谁的?

  • 代码
public static void test03(){
        Thread.setDefaultUncaughtExceptionHandler((t,e) -> {
            if(e instanceof RuntimeException){
                System.out.println("### default catch " + e);
            }
        });
        ExecutorService exec2 = Executors.newCachedThreadPool(new MyHandlerThreadFactory());
        exec2.execute(new ExceptionThreadDemo());
        exec2.shutdown();
    }
  • 输出:
MyHandlerThreadFactory creating new Thread ==== Begin ====
MyHandlerThreadFactory created Thread[Thread-0,5,main]
eh = indi.swaaa@qq.com1a93a7ca
MyHandlerThreadFactory creating new Thread ==== End ====
run by -> Thread[Thread-0,5,main]
eh -> indi.swaaa@qq.com1a93a7ca
### MyUncaughtExceptionHandler catch java.lang.RuntimeException: 这是自定义的 RuntimeException ... 
  • 结论:
  1. 没有"### default catch "的输出,说明没有进入 default 板块,而是走自定义的那一个
  2. 这个默认的处理器只有在不存在线程专有的未捕获异常处理器的情况下才会被调用。

=== 点击查看top目录 ===

三、代码剖析

3.1 uncaughtExceptionHandler 与 defaultUncaughtExceptionHandler

public class Thread implements Runnable {
...
    // null unless explicitly set
    private volatile UncaughtExceptionHandler uncaughtExceptionHandler;

    // null unless explicitly set
    private static volatile UncaughtExceptionHandler defaultUncaughtExceptionHandler;
...
}
  1. uncaughtExceptionHandler 对单个 thread 设置的
  2. defaultUncaughtExceptionHandler 默认的

=== 点击查看top目录 ===

3.2 setUncaughtExceptionHandler 方法

  • java.lang.Thread#setUncaughtExceptionHandler 方法
    public void setUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
        checkAccess();
        uncaughtExceptionHandler = eh;
    }
  • 设置变量 uncaughtExceptionHandler ,使其不为 null

=== 点击查看top目录 ===

3.3 setDefaultUncaughtExceptionHandler 方法

  • java.lang.Thread#setDefaultUncaughtExceptionHandler 方法
    public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            sm.checkPermission(
                new RuntimePermission("setDefaultUncaughtExceptionHandler")
                    );
        }

         defaultUncaughtExceptionHandler = eh;
     }
  • 设置默认变量 setDefaultUncaughtExceptionHandler ,使其不为 null

=== 点击查看top目录 ===

3.4 dispatchUncaughtException 方法

  • java.lang.Thread#dispatchUncaughtException
    /**
     * Dispatch an uncaught exception to the handler. This method is
     * intended to be called only by the JVM.
     */
    private void dispatchUncaughtException(Throwable e) {
        getUncaughtExceptionHandler().uncaughtException(this, e);
    }

dispatchUncaughtException 方法由 JVM 控制,也就是子线程抛出异常之后,调用的Exception处理方法。
=== 点击查看top目录 ===

3.5 getUncaughtExceptionHandler 方法

  • java.lang.Thread#getUncaughtExceptionHandler
    public UncaughtExceptionHandler getUncaughtExceptionHandler() {
        return uncaughtExceptionHandler != null ?
            uncaughtExceptionHandler : group;
    }
  1. 该方法用于获取真正处理异常的 Handler 方法
  2. uncaughtExceptionHandler != null ? uncaughtExceptionHandler : group; 若 thread 定制了 Handler ,那么走 uncaughtExceptionHandler, 如果没有指定,就返回线程组 group。
  3. 注意:ThreadGroup 实现了 Thread.UncaughtExceptionHandler
public class ThreadGroup implements Thread.UncaughtExceptionHandler {}

=== 点击查看top目录 ===

3.5.1 为什么== 实战4 ==定制的优先与默认的?
  1. 因为定制了 handler,所以在 getUncaughtExceptionHandler 的时候,走了定制的。
3.5.2 若只有 default,流程如何走?
  1. getUncaughtExceptionHandler() 方法,返回了 group
3.5.3 uncaughtException 方法
  • java.lang.ThreadGroup#uncaughtException
public void uncaughtException(Thread t, Throwable e) {
        if (parent != null) {
            parent.uncaughtException(t, e);
        } else {
            Thread.UncaughtExceptionHandler ueh =
                Thread.getDefaultUncaughtExceptionHandler(); // 看这里
            if (ueh != null) {
                ueh.uncaughtException(t, e);
            } else if (!(e instanceof ThreadDeath)) {
                System.err.print("Exception in thread \""
                                 + t.getName() + "\" ");
                e.printStackTrace(System.err);
            }
        }
    }
  1. 内部调用了 Thread.getDefaultUncaughtExceptionHandler()
  2. 递归调用,从老一辈开始
    === 点击查看top目录 ===

3.6 getDefaultUncaughtExceptionHandler 方法

  • java.lang.Thread#getDefaultUncaughtExceptionHandler
    public static UncaughtExceptionHandler getDefaultUncaughtExceptionHandler(){
        return defaultUncaughtExceptionHandler;
    }

四、番外篇

上一章节:【线程】ThreadGroup 实战与剖析 (十七)