歡迎來到《並發王者課》,本文是該系列文章中的第9篇。
在本篇文章中,我將為你介紹線程中異常的處理方式以及uncaughtExceptionHandler用法。
一、新線程中的異常去哪了
應用程序在執行過程中,難免會出現各種意外錯誤,如果我們沒有對錯誤進行捕獲處理,會直接影響應用的運行結果,甚至導致應用崩潰。而在應用異常處理中,多線程的異常處理是比較重要又容易犯錯的地方。
接下來,我們通過一段代碼模擬一種常見的多線程異常處理方式。
在下面的代碼中,我們在主線程中創建了新線程nezhaThread,並期望在主線程中捕獲新線程中拋出的異常:
public static void main(String[] args) {
Thread neZhaThread = new Thread() {
public void run() {
throw new RuntimeException("我是哪吒,我被圍攻了!");
}
};
// 嘗試捕獲線程拋出的異常
try {
neZhaThread.start();
} catch (Exception e) {
System.out.println("接收英雄異常:" + e.getMessage());
}
}
運行結果如下:
Exception in thread "Thread-0" java.lang.RuntimeException: 我是哪吒,我被圍攻了!
at cn.tao.king.juc.execises1.ExceptionDemo$1.run(ExceptionDemo.java:7)
Process finished with exit code 0
對於多線程新手來說,可能並不能直接看出其中的不合理。然而,從運行的結果中可以看到,沒有輸出“接收英雄異常”關鍵字。也就是說,主線程並未能捕獲新線程的異常。 那這是為什么呢?
理解這一現象,首先要從線程的本質出發。在Java中,每個線程所運行的都是獨立運行的代碼片段,如果我們沒有主動提供線程間通信和協作的機制,那么它們彼此之間是隔離的。
換句話說,每個線程都要在自己的閉環內完成全部的任務處理,包括對異常的處理,如果出錯了但你沒有主動處理異常,那么它們會按照既定的流程自我了結。
二、多線程中的異常處理方式
1. 從主線程看異常的處理
為了理解多線程中的錯誤處理方式,我們先看常見的主線程是如何處理錯誤的,畢竟相對於多線程,單一的主線程更容易讓人理解。
public static void main(String[] args) {
throw new NullPointerException();
}
很明顯,上面這段代碼將會拋出下面錯誤信息:
Exception in thread "main" java.lang.NullPointerException
at cn.tao.king.juc.execises1.ExceptionDemo.main(ExceptionDemo.java:21)
對於類似於空指針錯誤的堆棧信息,相信你一定並不陌生。在主線程中處理這樣的異常很簡單,通過編寫try
、catch
代碼塊即可。但其實,除了這種方式外,我們還可以通過定義uncaughtExceptionHandler
來處理主線程中的異常。
public static void main(String[] args) {
Thread.setDefaultUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
throw new NullPointerException();
}
// 自定義錯誤處理
static class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
public void uncaughtException(Thread t, Throwable e) {
System.out.println("出錯了!線程名:" + t.getName() + ",錯誤信息:" + e.getMessage());
}
}
輸出結果如下:
出錯了!線程名:main,錯誤信息:null
Process finished with exit code 1
你看,我們已經地捕獲了異常。然而,你可能會疑惑為什么Thread.UncaughtExceptionHandler可以自定義錯誤處理?說到這,就不得不提Java中的異常處理方式,如下圖所示:
在Java中,我們經常可以看到空指針那樣的錯誤的堆棧信息,然而這個堆棧實則是線程在出錯的情況下 “不得已” 才輸出來的。從圖中我們可以看到:
- 當線程出錯時,首先會檢查當前線程是否指定了錯誤處理器;
- 如果當前線程沒有指定錯誤處理器,則繼續檢查其所在的線程組是否指定(注意,前面我們已經說過,每個線程都是有線程組的);
- 如果當前線程的線程組也沒有指定,則繼續檢查其父線程是否指定;
- 如果父線程同樣沒有指定錯誤處理器,則最后檢查默認處理是否設置;
- 如果默認處理器也沒有設置,那么將不得不輸出錯誤的堆棧信息。
2. 多線程間的異常處理
不要忘記,主線程也是線程,所以當你理解了主線程的錯誤處理方式后,你也就理解了子線程中的異常處理方式,它們和主線程是相同的。在主線程中,我們可以通過Thread.setDefaultUncaughtExceptionHandler
來設置自定義異常處理器。而在新的子線程中,則可以通過線程對象直接指定異常處理器,比如我們給前面的neZhaThread線程設置異常處理器:
neZhaThread.setName("哪吒");
neZhaThread.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
那么,設置處理器后的線程異常信息則輸出如下:
出錯了!線程名:哪吒,錯誤信息:我是哪吒,我被圍攻了!
Process finished with exit code 0
你看,通過定義uncaughtExceptionHandler
,我們已經捕獲並處理了新線程拋出的異常。
3. 理解UncaughtExceptionHandler
從上面的代碼中,相信你已經直觀地理解UncaughtExceptionHandler用法。在Java中,UncaughtExceptionHandler用於處理線程突然異常終止的情況。當線程因某種原因拋出未處理的異常時,JVM虛擬機將會通過線程中的getUncaughtExceptionHandler
查詢該線程的錯誤處理器,並將該線程和異常信息作為參數傳遞過去。如果該線程沒有指定錯誤處理器,將會按照上圖所示的流程繼續查找。
三、 定義uncaughtExceptionHandler的三個層面
1. 定義默認異常處理器
默認的錯誤處理器可以作為線程異常的兜底處理器,在線程和線程組未指定異常處理器時,可以使用默認的異常處理器。
Thread.setDefaultUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
2. 自定義特定的異常處理器
如果某個線程需要特定的處理器時,通過線程對象指定異常處理器是個很不錯的選擇。當然,這種異常處理器不可以與其他線程共享。
neZhaThread.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
3. 繼承ThreadGroup
通過繼承ThreadGroup並覆寫uncaughtException
可以重設當前線程組的異常處理器邏輯。不過要注意的是,覆寫線程組的行為並不常見,使用時需要慎重。
public class MyThreadGroupDemo extends ThreadGroup{
public MyThreadGroupDemo(String name) {
super(name);
}
@Override
public void uncaughtException(Thread t, Throwable e) {
// 在這里重寫線程組的異常處理邏輯
System.out.println("出錯了!線程名:" + t.getName() + ",錯誤信息:" + e.getMessage());
}
}
小結
以上就是關於線程異常處理的全部內容,在本文中我們介紹了多線程異常的處理方式以及uncaughtExceptionHandler
的用法。對於多線程的異常處理應該記住:
- 線程內部的異常應盡可能在其內部解決;
- 如果主線程需要捕獲子線程異常,不可以使用
try
、catch
,而是要使用uncaughtExceptionHandler
。當然,已經在子線程內部捕獲的異常,主線程將無法捕獲。
正文到此結束,恭喜你又上了一顆星✨
夫子的試煉
- 編寫代碼了解並體驗uncaughtExceptionHandler用法。
延伸閱讀
關於作者
關注公眾號【庸人技術笑談】,獲取及時文章更新。記錄平凡人的技術故事,分享有品質(盡量)的技術文章,偶爾也聊聊生活和理想。不販賣焦慮,不做標題黨。
如果本文對你有幫助,歡迎點贊、關注、監督,我們一起從青銅到王者。