Java thread中對異常的處理策略


轉載:http://shmilyaw-hotmail-com.iteye.com/blog/1881302

前言

    想討論這個話題有一段時間了。記得幾年前的時候去面試,有人就問過我一個類似的問題。就是java thread中對於異常的處理情況。由於java thread本身牽涉到並發、鎖等相關的問題已經夠復雜了。再加上異常處理這些東西,使得它更加特殊。 概括起來,不外乎是三個主要的問題。1. 在java啟動的線程里可以拋出異常嗎? 2. 在啟動的線程里可以捕捉異常嗎? 3. 如果可以捕捉異常,對於checked exception和unchecked exception,他們分別有什么的處理方式呢?

     現在, 我們就一個個的來討論。

線程里拋出異常

    我們可以嘗試一下在線程里拋異常。按照我們的理解,假定我們要在某個方法里拋異常,需要在該定義的方法頭也加上聲明。那么一個最簡單的方式可能如下:

Java代碼   收藏代碼
  1. public class Task implements Runnable {  
  2.   
  3.     @Override  
  4.     public void run() throws Exception {  
  5.         int number0 = Integer.parseInt("1");  
  6.         throw new Exception("Just for test");  
  7.     }  
  8. }  

    可是,如果我們去編譯上面這段代碼,會發現根本就編譯不過去的。系統報的錯誤是:

Java代碼   收藏代碼
  1. Task.java:3: error: run() in Task cannot implement run() in Runnable  
  2.     public void run() throws Exception {  
  3.                 ^  
  4.   overridden method does not throw Exception  
  5. 1 error  

     由此我們發現這種方式行不通。也就是說,在線程里直接拋異常是不行的。可是,這又會引出一個問題,如果我們在線程代碼里頭確實是產生了異常,那該怎么辦呢?比如說,我們通過一個線程訪問一些文件或者對網絡進行IO操作,結果產生了異常。或者說訪問某些資源的時候系統崩潰了。這樣的場景是確實可能會發生的,我們就需要針對這些情況進行進一步的討論。

異常處理的幾種方式

     在前面提到的幾種在線程訪問資源產生了異常的情況。我們可以看,比如說我們訪問文件系統的時候,會拋出IOException, FileNotFoundException等異常。我們在訪問的代碼里實際上是需要采用兩種方式來處理的。一種是在使用改資源的方法頭增加throws IOException, FileNotFoundException等異常的修飾。還有一種是直接在這部分的代碼塊增加try/catch部分。由前面我們的討論已經發現,在方法聲明加throws Exception的方式是行不通的。那么就只有使用try/catch這么一種方式了。

    另外,我們也知道,在異常的處理上,一般異常可以分為checked exception和unchecked exception。作為unchecked exception,他們通常是指一些比較嚴重的系統錯誤或者系統設計錯誤,比如Error, OutOfMemoryError或者系統直接就崩潰了。對於這種異常發生的時候,我們一般是無能為力也沒法恢復的。那么這種情況的發生,我們會怎么來處理呢?

checked exception

     在線程里面處理checked exception,按照我們以前的理解,我們是可以直接捕捉它來處理的。在一些thread的示例里我們也見過。比如說下面的一部分代碼:

Java代碼   收藏代碼
  1. import java.util.Date;  
  2. import java.util.concurrent.TimeUnit;  
  3.   
  4. public class FileLock implements Runnable {  
  5.     @Override  
  6.     public void run() {  
  7.         for(int i = 0; i < 10; i++) {  
  8.             System.out.printf("%s\n", new Date());  
  9.             try {  
  10.                 TimeUnit.SECONDS.sleep(1);  
  11.             } catch(InterruptedException e) {  
  12.                 System.out.printf("The FileClock has been interrupted");  
  13.             }  
  14.         }  
  15.     }  
  16. }  

     我們定義了一個線程執行代碼,並且在這里因為調用TimeUnit.SECONDS.sleep()方法而需要捕捉異常。因為這個方法本身就會拋出InterruptedException,我們必須要用try/catch塊來處理。

     我們啟動該線程並和它交互的代碼如下:

Java代碼   收藏代碼
  1. import java.util.concurrent.TimeUnit;  
  2.   
  3. public class Main {  
  4.     public static void main(String[] args) {  
  5.         // Creates a FileClock runnable object and a Thread  
  6.         // to run it  
  7.         FileClock clock=new FileClock();  
  8.         Thread thread=new Thread(clock);  
  9.           
  10.         // Starts the Thread  
  11.         thread.start();  
  12.         try {  
  13.             // Waits five seconds  
  14.             TimeUnit.SECONDS.sleep(5);  
  15.         } catch (InterruptedException e) {  
  16.             e.printStackTrace();  
  17.         };  
  18.         // Interrupts the Thread  
  19.         thread.interrupt();  
  20.     }  
  21. }  

     這部分的代碼是啟動FileLock線程並嘗試去中斷它。我們可以發現在運行的時候FileLock里面執行的代碼能夠正常的處理異常。

    因此,在thread里面,如果要處理checked exception,簡單的一個try/catch塊就可以了。

unchecked exception

     對於這種unchecked exception,相對來說就會不一樣一點。實際上,在Thread的定義里有一個實例方法:setUncaughtExceptionHandler(UncaughtExceptionHandler). 這個方法可以用來處理一些unchecked exception。那么,這種情況的場景是如何的呢?

    setUncaughtExceptionHandler()方法相當於一個事件注冊的入口。在jdk里面,該方法的定義如下:

Java代碼   收藏代碼
  1. public void setUncaughtExceptionHandler(UncaughtExceptionHandler eh) {  
  2.     checkAccess();  
  3.     uncaughtExceptionHandler = eh;  
  4. }  

     而UncaughtExceptionHandler則是一個接口,它的聲明如下:

Java代碼   收藏代碼
  1. public interface UncaughtExceptionHandler {  
  2.     /** 
  3.      * Method invoked when the given thread terminates due to the 
  4.      * given uncaught exception. 
  5.      * <p>Any exception thrown by this method will be ignored by the 
  6.      * Java Virtual Machine. 
  7.      * @param t the thread 
  8.      * @param e the exception 
  9.     */  
  10.     void uncaughtException(Thread t, Throwable e);  
  11. }  

     在異常發生的時候,我們傳入的UncaughtExceptionHandler參數的uncaughtException方法會被調用。

     綜合前面的討論,我們這邊要實現handle unchecked exception的方法的具體步驟可以總結如下:

1. 定義一個類實現UncaughtExceptionHandler接口。在實現的方法里包含對異常處理的邏輯和步驟。

2. 定義線程執行結構和邏輯。這一步和普通線程定義一樣。

3. 在創建和執行改子線程的方法里在thread.start()語句前增加一個thread.setUncaughtExceptionHandler語句來實現處理邏輯的注冊。

    下面,我們就按照這里定義的步驟來實現一個示例:

    首先是實現UncaughtExceptionHandler接口部分:

Java代碼   收藏代碼
  1. import java.lang.Thread.UncaughtExceptionHandler;  
  2.   
  3. public class ExceptionHandler implements UncaughtExceptionHandler {  
  4.     public void uncaughtException(Thread t, Throwable e) {  
  5.         System.out.printf("An exception has been captured\n");  
  6.         System.out.printf("Thread: %s\n", t.getId());  
  7.         System.out.printf("Exception: %s: %s\n",   
  8.                 e.getClass().getName(), e.getMessage());  
  9.         System.out.printf("Stack Trace: \n");  
  10.         e.printStackTrace(System.out);  
  11.         System.out.printf("Thread status: %s\n", t.getState());  
  12.     }  
  13. }  

     這里我們添加的異常處理邏輯很簡單,只是把線程的信息和異常信息都打印出來。

    然后,我們定義線程的內容,這里,我們故意讓該線程產生一個unchecked exception:

Java代碼   收藏代碼
  1. public class Task implements Runnable {  
  2.   
  3.     @Override  
  4.     public void run() {  
  5.         int number0 = Integer.parseInt("TTT");  
  6.     }  
  7. }  

     從這代碼里我們可以看到,Integer.parseInt()里面的參數是錯誤的,肯定會拋出一個異常來。

    現在,我們再把創建線程和注冊處理邏輯的部分補上來:

Java代碼   收藏代碼
  1. public class Main {  
  2.     public static void main(String[] args) {  
  3.         Task task = new Task();  
  4.         Thread thread = new Thread(task);  
  5.         thread.setUncaughtExceptionHandler(new ExceptionHandler());  
  6.         thread.start();  
  7.     }  
  8. }  

     現在我們去執行整個程序,會發現有如下的結果:

Java代碼   收藏代碼
  1. An exception has been captured  
  2. Thread: 8  
  3. Exception: java.lang.NumberFormatException: For input string: "TTT"  
  4. Stack Trace:   
  5. java.lang.NumberFormatException: For input string: "TTT"  
  6.     at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)  
  7.     at java.lang.Integer.parseInt(Integer.java:492)  
  8.     at java.lang.Integer.parseInt(Integer.java:527)  
  9.     at Task.run(Task.java:5)  
  10.     at java.lang.Thread.run(Thread.java:722)  
  11. Thread status: RUNNABLE  

    這部分的輸出正好就是我們前面實現UncaughtExceptionHandler接口的定義。

    因此,對於unchecked exception,我們也可以采用類似事件注冊的機制做一定程度的處理。

總結

     Java thread里面關於異常的部分比較奇特。你不能直接在一個線程里去拋出異常。一般在線程里碰到checked exception,推薦的做法是采用try/catch塊來處理。而對於unchecked exception,比較合理的方式是注冊一個實現UncaughtExceptionHandler接口的對象實例來處理。這些細節的東西如果沒有碰到過確實很難回答。


免責聲明!

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



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