1. 程式人生 > >學習筆記之執行緒、Thread類和執行緒終止相關整理(下)——執行緒異常&JVM停止

學習筆記之執行緒、Thread類和執行緒終止相關整理(下)——執行緒異常&JVM停止

提到執行緒的中斷在某些情況下會丟擲InterruptedException異常,最終導致執行緒的終止。其實,執行緒也有可能由於其他異常原因造成終止,在某些情況下為了做一些妥善的處理,我們需要捕獲這些異常情況。看下面程式碼,覺得會怎樣?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class App { static class ExRun implements Runnable { public void run() { System.out.println("throwing..."
); throw new RuntimeException(); } } public static void main(String[] args) { try { new Thread(new ExRun()).start(); } catch (Exception e) { System.out.println("runtime ex. catched!"); } } }

會在控制檯打出”runtime ex. catched!”?你確定麼?不怕大家笑話,幾年我第一眼看類似這段程式碼的時候,竟然也認為這很理所當然。其實,這也許不是年頭的問題,是是否仔細思考和經驗問題。只要仔細想一想,既然已經new了一個Thread並且讓他start()去了,異常又不是在new的過程中或者start的過程中出來的,拋不拋異常關這個主執行緒什麼事兒。

那如果一個被主執行緒建立的新執行緒除了什麼“故障”,就讓其悄悄地自生自滅麼?有沒有補救方案呢?有的。在JavaSE5之前,這個問題直接交給執行緒組(ThreadGroup)了事兒,但在這之後就有了java.lang.Thread$UncaughtExceptionHandler($是內部關係,後者是前者的內部類/介面)這麼一個interface,裡面只有一個方法:

void uncaughtException(Thread t, Throwable e);

這個方法就是負責處理異常的,Thread引數不說了,那這個Throwable又是啥,這個就是Exception的老爸了,不瞭解的異常類層次結構的,這兒您就可以查查去了。既然是Throwable,其實也就不只是Exception了,Error也會被處理到。

接著就簡單說下怎麼用,Thread類的兩個方法:

  • public void setUncaughtExceptionHandler(UncaughtExceptionHandler eh)
  • public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh)

第一個是例項方法,需要針對某個Thread物件使用,而第二個是靜態方法,設定全域性的預設處理方法,但這兩個不衝突,在Thread中是兩個獨立的handler屬性。但畢竟預設的和非預設的還不一樣,有個順序問題,這裡又不得不再提到執行緒組(ThreadGroup)

在JavaSE5之前,沒有UncaughtExceptionHandler這個東西,所以出錯會直接交給Thread的ThreadGroup物件屬性group處理,因為ThreadGroup有處理異常的方法,而UncaughtExceptionHandler出來後,ThreadGroup也實現了這個介面。按照文件註釋說明:

  1. 優先檢查執行緒的uncaughtExceptionHandler
  2. 否則交給group處理
  3. 最後才可能輪到defaultUncaughtExceptionHandler

而看原始碼中執行緒組的處理實際上是追溯一個parent(也是ThreadGroup類物件)鏈:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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); } } }

最後為空時,採用全域性預設的的handler。

執行緒異常就說道這,已經說了不少了,下面簡單說兩句JVM的退出/停止。

通常我們在課本上都會知道java.lang.System類有個方法是exit(int),這個會讓程式退出,而實際上這個方法實際上是java.lang.Runtime類exit(int)的封裝(其實System還有像gc()等很多方法,實際執行者都是這個java.lang.Runtime類物件),意義是告訴當前執行環境關閉執行,也就通常被理解為JVM“關閉”。

說下Runtime類,這個類是個單例模式的類,即從頭到尾程式只能獲得同一個物件引用,即呼叫靜態方法Runtime.getRuntime()所得。Runtime類負責和執行時環境相關的各種任務,有很多有趣的方法,比如很多應用(如Tomcat)中某些執行緒池中執行緒數目的確定和當前執行環境處理器個數有關,則可通過Runtime的availableProcessors()方法獲得。這裡重點說下exit()方法。

還有一個概念需要說下,就是ShutdownHook,這個聽起來“高階洋氣上檔次”的東西實際上就是一個Thread,但它的特殊就在於是一個hook,在JVM關閉(exit)的時候才會執行到。就如執行緒和中斷的關係一樣,JVM和exit()也是一樣,exit()的執行並不是虛擬機器馬上關掉,而是安排結束時所要執行任務,按文件說明分上下兩個過程,前面的過程就是shutdown對應Thread執行的過程。要想讓虛擬機器優雅的結束,exit()安排執行shutdownHook清理環境是必要的,還說Tomcat吧,終止的時候也會執行註冊過的hook。執行hook的時候,通常守護/非守護執行緒也都仍然可以執行,但對hookd的新增和移除則不可以了。

Runtime和shutdonwHook關聯起來的方法,主要就是這倆了:

  • public void addShutdownHook(Thread hook)
  • public boolean removeShutdownHook(Thread hook)

具體用法不用多說估計看文件都能懂,需要注意的地方就是,想要remove必需在add的時候留有hook的Thread物件引用。

和Thread的stop()類似,Runtime也有halt()方法,但這是一個“暴力停機”的方法,不會考慮hook的執行,如果情況允許,exit()還是優雅的選擇。

到此,對執行緒相關的基本介紹先告一段落,讀者們覺得需要補充的地方,我真得是非常歡迎補充、拍磚和指正的,對你我的學習成長都有很大好處。:)