Java子線程中的異常處理(通用)


在普通的單線程程序中,捕獲異常只需要通過try ... catch ... finally ...代碼塊就可以了。那么,在並發情況下,比如在父線程中啟動了子線程,如何正確捕獲子線程中的異常,從而進行相應的處理呢?

常見錯誤

也許有人會覺得,很簡單嘛,直接在父線程啟動子線程的地方try ... catch一把就可以了,其實這是不對的。

原因分析

讓我們回憶一下Runnable接口的run方法的完整簽名,因為沒有標識throws語句,所以方法是不會拋出checked異常的。至於RuntimeException這樣的unchecked異常,由於新線程由JVM進行調度執行,如果發生了異常,也不會通知到父線程。

public abstract void run()
 
        

解決辦法

那么,如何正確處理子線程中的異常呢?樓主想到了3種常用方法,分享給大家。
前2種方法都是在子線程中處理,第3種方法是在父線程中處理。
具體用哪一種方法,取決於這個異常是否適合在子線程中處理。例如有些異常更適合由調用方(父線程)處理,那么此時就應當用第3種方法。

方法一:子線程中try... catch...

最簡單有效的辦法,就是在子線程的執行方法中,把可能發生異常的地方,用try ... catch ... 語句包起來。
子線程代碼:
 1 public class ChildThread implements Runnable {
 2     public void run() {
 3         doSomething1();
 4         try {
 5             // 可能發生異常的方法
 6             exceptionMethod();
 7         } catch (Exception e) {
 8             // 處理異常
 9             System.out.println(String.format("handle exception in child thread. %s", e));
10         }
11         doSomething2();
12     }
13 }

方法二:為線程設置“未捕獲異常處理器”UncaughtExceptionHandler

為線程設置異常處理器。具體做法可以是以下幾種:
(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     }
 7 
 8     public void run() {
 9         Thread.currentThread().setUncaughtExceptionHandler(exceptionHandler);
10         System.out.println("do something 1");
11         exceptionMethod();
12         System.out.println("do something 2");
13     }
14 
15     public static class ChildThreadExceptionHandler implements Thread.UncaughtExceptionHandler {
16         public void uncaughtException(Thread t, Throwable e) {
17             System.out.println(String.format("handle exception in child thread. %s", e));
18         }
19     }
20 }
或者,設置所有線程的默認異常處理器
 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
 
        

方法三:通過Future的get方法捕獲異常(推薦)

使用線程池提交一個能獲取到返回信息的方法,也就是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種方式,可以方便地在父線程中根據子線程拋出的異常做適當的處理(自己處理,或者忽略,或者繼續向上層調用方拋異常)。
其實樓主還想到了另外幾個特定場景下的解決辦法,改天再分析,謝謝大家支持~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM