在普通的單線程程序中,捕獲異常只須要經過try ... catch ... finally ...代碼塊就能夠了。那麼,在併發狀況下,好比在父線程中啓動了子線程,如何正確捕獲子線程中的異常,從而進行相應的處理呢?java
也許有人會以爲,很簡單嘛,直接在父線程啓動子線程的地方try ... catch一把就能夠了,其實這是不對的。併發
讓咱們回憶一下Runnable接口的run方法的完整簽名,由於沒有標識throws語句,因此方法是不會拋出checked異常的。至於RuntimeException這樣的unchecked異常,因爲新線程由JVM進行調度執行,若是發生了異常,也不會通知到父線程。spa
public abstract void run()
那麼,如何正確處理子線程中的異常呢?樓主想到了3種經常使用方法,分享給你們。
前2種方法都是在子線程中處理,第3種方法是在父線程中處理。
具體用哪種方法,取決於這個異常是否適合在子線程中處理。例若有些異常更適合由調用方(父線程)處理,那麼此時就應當用第3種方法。
最簡單有效的辦法,就是在子線程的執行方法中,把可能發生異常的地方,用try ... catch ... 語句包起來。
子線程代碼:
爲線程設置異常處理器。具體作法能夠是如下幾種:
(1)Thread.setUncaughtExceptionHandler設置當前線程的異常處理器;
(2)Thread.setDefaultUncaughtExceptionHandler爲整個程序設置默認的異常處理器;
若是當前線程有異常處理器(默認沒有),則優先使用該UncaughtExceptionHandler類;不然,若是當前線程所屬的線程組有異常處理器,則使用線程組的
UncaughtExceptionHandler;不然,使用全局默認的DefaultUncaughtExceptionHandler;若是都沒有的話,子線程就會退出。
注意:子線程中發生了異常,若是沒有任何類來接手處理的話,是會直接退出的,而不會記錄任何日誌。
因此,若是什麼都不作的話,是會出現子線程任務既沒執行成功,也沒有任何日誌提示的「詭異」現象的。
設置當前線程的異常處理器:
或者,設置全部線程的默認異常處理器
1 public class ChildThread implements Runnable { 2 private static ChildThreadExceptionHandler exceptionHandler; 3 4 static { 5 exceptionHandler = new ChildThreadExceptionHandler(); 6 Thread.setDefaultUncaughtExceptionHandler(exceptionHandler); 7 } 8 9 public void run() { 10 System.out.println("do something 1"); 11 exceptionMethod(); 12 System.out.println("do something 2"); 13 } 14 15 private void exceptionMethod() { 16 throw new RuntimeException("ChildThread exception"); 17 } 18 19 public static class ChildThreadExceptionHandler implements Thread.UncaughtExceptionHandler { 20 public void uncaughtException(Thread t, Throwable e) { 21 System.out.println(String.format("handle exception in child thread. %s", e)); 22 } 23 } 24 }
命令行輸出:
do something 1
handle exception in child thread. java.lang.RuntimeException: ChildThread exception
使用線程池提交一個能獲取到返回信息的方法,也就是ExecutorService.submit(Callable)
在submit以後能夠得到一個線程執行結果的Future對象,而若是子線程中發生了異常,經過future.get()獲取返回值時,能夠捕獲到
ExecutionException異常,從而知道子線程中發生了異常。
子線程代碼:
1 public class ChildThread implements Callable<String> { 2 public String call() throws Exception { 3 System.out.println("do something 1"); 4 exceptionMethod(); 5 System.out.println("do something 2"); 6 return "test result"; 7 } 8 9 private void exceptionMethod() { 10 throw new RuntimeException("ChildThread1 exception"); 11 } 12 }
父線程代碼:
1 public class Main { 2 public static void main(String[] args) { 3 ExecutorService executorService = Executors.newFixedThreadPool(8); 4 Future future = executorService.submit(new ChildThread()); 5 try { 6 future.get(); 7 } catch (InterruptedException e) { 8 System.out.println(String.format("handle exception in child thread. %s", e)); 9 } catch (ExecutionException e) { 10 System.out.println(String.format("handle exception in child thread. %s", e)); 11 } finally { 12 if (executorService != null) { 13 executorService.shutdown(); 14 } 15 } 16 } 17 }
命令行輸出:
do something 1
handle exception in child thread. java.util.concurrent.ExecutionException: java.lang.RuntimeException: ChildThread1 exception
以上就是3種通用的Java子線程異常處理方法。具體使用哪一種,取決於異常是否適合在子線程中處理,樓主更推薦第3種方式,能夠方便地在父線程中根據子線程拋出的異常作適當的處理(本身處理,或者忽略,或者繼續向上層調用方拋異常)。其實樓主還想到了另外幾個特定場景下的解決辦法,改天再分析,謝謝你們支持~