捕獲線程運作時異常API
void setUncaughtExceptionHandler(UncaughtExceptionHandler eh)
為某個線程指定uncaughtExceptionHandler
static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh)
設定全局的uncaughtExceptionHandler
static UncaughtExceptionHandler getDefaultUncaughtExceptionHandler()
擷取全局的uncaughtExceptionHandler
UncaughtExceptionHandler getUncaughtExceptionHandler()
擷取特定線程的uncaughtExceptionHandler
UncaughtExceptionHandler介紹
線程在執行單元中是不允許抛出checked異常的,線上程的運作上下文中,派生它的線程無法直接擷取它運作中得異常資訊。對此java提供了UncaughtExceptionHandler接口,當線程運作過程中出現異常時會回調UncaughtExceptionHandler接口,進而得知是哪個線程運作時出了什麼樣的錯。
@FunctionalInterface
public interface UncaughtExceptionHandler {
void uncaughtException(Thread t, Throwable e);
}
private void dispatchUncaughtException(Throwable e) {
getUncaughtExceptionHandler().uncaughtException(this, e);
}
UncaughtExceptionHandler 是一個FunctionalInterface,隻有一個抽象方法,當線程運作過程中出現異常時,JVM會調用dispatchUncaughtException将對應線程的異常資訊傳遞給回調接口。
getUncaughtExceptionHandler源碼
public UncaughtExceptionHandler getUncaughtExceptionHandler() {
return uncaughtExceptionHandler != null ?
uncaughtExceptionHandler : group;
}
方法首先會判斷目前線程是否設定了uncaughtExceptionHandler ,有則執行線程自己的uncaughtException(),否則就到ThreadGroup中擷取。
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);
}
}
}
該線程如果有父ThreadGroup則直接調用父Group的uncaughtException(),如果設定了全局預設的UncaughtExceptionHandler,則調用uncaughtException(),既沒有設定全局UncaughtExceptionHandler也沒有父ThreadGroup則直接将異常資訊定向到System.err中。
UncaughtExceptionHandler 測試代碼
public static void main(String[] args) {
Thread.setDefaultUncaughtExceptionHandler((t,e)->{
System.out.println(t.currentThread().getName()+"抛出異常 " +e.getMessage());
e.printStackTrace();
});
new Thread(()->{
System.out.println(1/0);
},"異常線程1").start();
}
輸出
異常線程1抛出異常 / by zero
java.lang.ArithmeticException: / by zero
at ThreadException.ExceptionTest.lambda$1(ExceptionTest.java:11)
at java.lang.Thread.run(Unknown Source)
Hook線程介紹(鈎子線程)
JVM程序的退出是由于沒有活躍的非守護線程,或者收到了系統中斷信号,向JVM程式注入一個Hook線程,在JVM程序退出的時候,Hook線程才會啟動執行。可以通過Runtime向JVM注入多個Hook線程。
hook線程注入例子
public static void main(String[] args) {
Runtime.getRuntime()
.addShutdownHook(new Thread(()->{
System.out.println("hook test 1 running");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("hook test 1 exit");
},"HOOK線程1"));
Runtime.getRuntime()
.addShutdownHook(new Thread(()->{
System.out.println("hook test 2 running");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("hook test 2 exit");
},"HOOK線程2"));
System.out.println("main thread exit");
}
輸出
main thread exit
hook test 2 running
hook test 1 running
hook test 1 exit
hook test 2 exit
Hook線程注意事項及使用場景:
- Hook線程隻有在收到JVM程序退出信号的時候才會被執行。
- Hook線程可以執行一些資源釋放工作,比如關閉檔案句柄,socket連結等。
- 盡量不要在Hook線程中執行耗時長的操作,會導緻程式遲遲不能退出。