Java執行緒未捕獲例外處理 UncaughtExceptionHandler

2022-11-04 15:00:12

當一個執行緒在執行過程中丟擲了異常,並且沒有進行try..catch,那麼這個執行緒就會終止執行。
在Thread類中,提供了兩個可以設定執行緒未捕獲異常的全域性處理器,我們可以在處理器裡做一些工作,例如將異常資訊傳送到遠端伺服器。
雖然這可以捕獲到執行緒中的異常,但是並不能阻止執行緒停止執行。因此該線上程run方法裡try..catch的,還是要好好的進行try..catch。

從Thread類原始碼中可以看到這2個變數:

private volatile UncaughtExceptionHandler uncaughtExceptionHandler;

private static volatile UncaughtExceptionHandler defaultUncaughtExceptionHandler;

需要注意到區別,defaultUncaughtExceptionHandler是靜態的,我們可以呼叫此方法設定所有執行緒物件的例外處理器,而uncaughtExceptionHandler則是針對單個執行緒物件的例外處理器。

uncaughtExceptionHandler優先順序高於defaultUncaughtExceptionHandler。

Thread類提供了這2個變數的setter/getter:

public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
    SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        sm.checkPermission(
            new RuntimePermission("setDefaultUncaughtExceptionHandler")
          );
    }
     defaultUncaughtExceptionHandler = eh;
 }

    
public static UncaughtExceptionHandler getDefaultUncaughtExceptionHandler(){
    return defaultUncaughtExceptionHandler;
}


public UncaughtExceptionHandler getUncaughtExceptionHandler() {
    return uncaughtExceptionHandler != null ?
        uncaughtExceptionHandler : group;
}

public void setUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
    checkAccess();
    uncaughtExceptionHandler = eh;
}

可以看到,getUncaughtExceptionHandler()中進行了判斷,當uncaughtExceptionHandler為null時返回group。

我們來看下UncaughtExceptionHandler介面是怎麼宣告的:

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

我們只需要實現UncaughtExceptionHandler介面,重寫uncaughtException方法即可進行例外處理。

那麼JVM是怎麼檢測到執行緒發生異常,並將異常分發到處理器的呢?
對於這塊程式碼,JDK原始碼中看不到是如何處理的,可能需要翻閱hotspot原始碼,不過Thread類中提供了一個dispatchUncaughtException方法,將異常回撥到了uncaughtExceptionHandler中去處理。

private void dispatchUncaughtException(Throwable e) {
    getUncaughtExceptionHandler().uncaughtException(this, e);
}

很明顯,dispatchUncaughtException應該就是提供給hotspot進行JNI回撥的。
而對於defaultUncaughtExceptionHandler的呼叫,猜測應該是在hotspot中直接完成了。

接下來我們用範例來演示一下例外處理器的效果。

範例:

Thread thread = new Thread(() -> {
    System.out.println("run before");

    System.out.println("runing");
    if(1 == 1) {
        throw new IllegalStateException("exception");
    }

    System.out.println("run after");
});
thread.setUncaughtExceptionHandler((t, e) -> System.out.println("捕獲異常," + t.getName() + "," + e.getMessage()));
Thread.setDefaultUncaughtExceptionHandler((t, e) -> System.out.println("Default捕獲異常," + t.getName() + "," + e.getMessage()));
thread.start();

輸出:

run before
runing
捕獲異常,Thread-0,exception

可以看出,雖然兩個例外處理器都有設定,並且defaultUncaughtExceptionHandler是最後設定的,不過起效的是uncaughtExceptionHandler。

可以將thread.setUncaughtExceptionHandler(...);註釋掉:
輸出:

run before
runing
Default捕獲異常,Thread-0,exception

註釋後,defaultUncaughtExceptionHandler起效了,證明了uncaughtExceptionHandler優先順序高於defaultUncaughtExceptionHandler。