【線程】Thread.UncaughtExceptionHandler 實戰與剖析 (十八)

我的原則:先會用再說,內部慢慢來。
學以致用,根據場景學源碼


一、前言

1.1 架構

在這裏插入圖片描述

  • 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.sword.util.basic.exception.multiThread.MyUncaughtExceptionHandler@b81eda8
MyHandlerThreadFactory creating new Thread ==== End ====
run by -> Thread[Thread-0,5,main]
eh -> indi.sword.util.basic.exception.multiThread.MyUncaughtExceptionHandler@b81eda8
### 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.sword.util.basic.exception.multiThread.MyUncaughtExceptionHandler@1a93a7ca
MyHandlerThreadFactory creating new Thread ==== End ====
run by -> Thread[Thread-0,5,main]
eh -> indi.sword.util.basic.exception.multiThread.MyUncaughtExceptionHandler@1a93a7ca
### 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;
    }

四、番外篇

下一章節:【線程】ThreadPool 線程池 Executors 實戰 (十九)
上一章節:【線程】ThreadGroup 實戰與剖析 (十七)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章