1.Thread的默認異常處理
線程不允許拋出未捕獲的checked exception(比如sleep時的InterruptedException),也就是說各個線程需要自己把自己的checked exception處理掉。我們可以查看一下Thread類的run()方法聲明,方法聲明上沒有對拋出異常進行任何約束。
//Thread類中 @Override public void run() { if (target != null) { target.run();//實際上直接調用Runnable實例的run方法 } } //Runnable接口中 public abstract void run();
JVM的這種設計源自於這樣一種理念:“線程是獨立執行的代碼片斷,線程的問題應該由線程自己來解決,而不要委托到外部。”基於這樣的設計理念,在Java中,線程方法的異常(無論是checked還是unchecked exception),都應該在線程代碼邊界之內(run方法內)進行try catch並處理掉。換句話說,我們不能捕獲從線程中逃逸的異常。
2.未捕獲的異常哪去了?
一個異常被拋出后,如果沒有被捕獲處理,則會一直向上拋。異常一旦被Thread.run() 拋出后,就不能在程序中對異常進行捕獲,最終只能由JVM捕獲。
//不處理異常 public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { int i = 1 / 0;//發生異常 } }).start(); } //執行結果: Exception in thread "Thread-0" java.lang.ArithmeticException: / by zero at thread.thread.thread.Uncaught$1.run at java.lang.Thread.run ====================================================== //嘗試對異常進行捕獲 public static void main(String[] args) { try { new Thread(new Runnable() { @Override public void run() { int i = 1 / 0;//發生異常 } }).start(); } catch (Exception e) { System.out.println("捕獲線程拋出的異常!"); } } //執行結果: Exception in thread "Thread-0" java.lang.ArithmeticException: / by zero at thread.thread.thread.Uncaught$1.run at java.lang.Thread.run
上面的例子中,我們嘗試在main方法中對線程中拋出的異常進行捕獲,但是無濟於事。
3.JVM如何處理線程中拋出的異常
查看Thread類的源碼,我們可以看到有個dispatchUncaughtException方法,此方法就是用來處理線程中拋出的異常的。JVM會調用dispatchUncaughtException方法來尋找異常處理器(UncaughtExceptionHandler),處理異常。
// 向handler分派未捕獲的異常。該方法僅由JVM調用。 private void dispatchUncaughtException(Throwable e) { getUncaughtExceptionHandler().uncaughtException(this, e); } // 獲取用來處理未捕獲異常的handler,如果沒有設置則返回當前線程所屬的ThreadGroup public UncaughtExceptionHandler getUncaughtExceptionHandler() { return uncaughtExceptionHandler != null ? uncaughtExceptionHandler : group; }
UncaughtExceptionHandler必須顯示的設置,否則默認為null。若為null,則使用線程默認的handler,即該線程所屬的ThreadGroup。ThreadGroup自身就是一個handler,查看ThreadGroup的源碼就可以發現,ThreadGroup實現了Thread.UncaughtExceptionHandler接口,並實現了默認的處理方法。默認的未捕獲異常處理器處理時,會調用 System.err 進行輸出,也就是直接打印到控制台了。
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)) { // 沒有配置handler時,默認直接打印到控制台 System.err.print("Exception in thread \"" + t.getName() + "\" "); e.printStackTrace(System.err); } } }
由此可知,最終JVM是調用未捕獲的異常處理器的uncaughtException()方法來處理異常的,並且是直接打印到控制台。
4.如何自定義處理異常
那么,如果我們要自己處理異常,該怎么辦呢?通過前面的分析,我們已經知道了線程會使用默認的未捕獲異常處理器來處理異常。自然我們可以想到,是否可以自定義未捕獲異常處理器,覆蓋掉默認的捕獲異常處理器。實際上,Thead確實已經提供了一個setUncaughtExceptionHandler方法,我們只需要將自定義未捕獲異常處理器作為參數傳入進入就可以了。
public static void main(String[] args) { Thread thread = new Thread(new Runnable() { @Override public void run() { int i = 1 / 0;//發生異常 } }); //自定義未捕獲異常處理器 thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { @Override public void uncaughtException(Thread t, Throwable e) { System.out.println("嘗試捕獲線程拋出的異常!"); } }); thread.start(); } //執行結果: 嘗試捕獲線程拋出的異常!
5.線程池中自定義處理異常
要自定義處理異常,只需要為線程提供一個自定義的UncaughtExceptionHandler。而在線程池中,該如何批量的為所有線程設置UncaughtExceptionHandler呢?我們知道,線程池中的線程是由線程工廠創建的。我們可以跟蹤ThreadPoolExecutor構造方法的源碼,最終定位到DefaultThreadFactory類,該類中有個newThread()方法,這就是線程產生的源頭了。
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) { this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), defaultHandler); } //Executors類中 public static ThreadFactory defaultThreadFactory() { return new DefaultThreadFactory(); } //DefaultThreadFactory類中 public Thread newThread(Runnable r) { Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0); if (t.isDaemon()) t.setDaemon(false); if (t.getPriority() != Thread.NORM_PRIORITY) t.setPriority(Thread.NORM_PRIORITY); return t; }
找到了線程創建的源頭,一切就OK了。我們可以實現一個自己的線程工廠,並覆蓋默認的newThread方法,為新創建的線程提供一個UncaughtExceptionHandler,即可達到我們的目的。由於DefaultThreadFactory是Executors類的內部類,我們不能直接繼承該類,只能實現該工廠類的接口——ThreadFactory接口,來實現我們自己的線程工廠。
class MyThreadFactory implements ThreadFactory { @Override public Thread newThread(Runnable r) { Thread t = new Thread(); //自定義UncaughtExceptionHandler t.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler()); return t; } }
需要注意的是,只有通過execute()方法提交任務,才能將它拋出的異常交給未捕獲異常處理器。而通過submit()方法提交的任務,無論是拋出未檢查異常還是已檢查異常,都將被認為是任務返回狀態的一部分。如果一個由submit()方法提交的任務由於拋出了異常而結束,該異常將被Future.get()封裝在ExecutionException中重新拋出。
參考: