前言
想討論這個話題有一段時間了。記得幾年前的時候去面試,有人就問過我一個相似的問題。就是java thread中對於異常的處理狀況。因爲java thread自己牽涉到併發、鎖等相關的問題已經夠複雜了。再加上異常處理這些東西,使得它更加特殊。 歸納起來,不外乎是三個主要的問題。1. 在java啓動的線程裏能夠拋出異常嗎? 2. 在啓動的線程裏能夠捕捉異常嗎? 3. 若是能夠捕捉異常,對於checked exception和unchecked exception,他們分別有什麼的處理方式呢? java
如今, 咱們就一個個的來討論。 面試
線程裏拋出異常
咱們能夠嘗試一下在線程裏拋異常。按照咱們的理解,假定咱們要在某個方法裏拋異常,須要在該定義的方法頭也加上聲明。那麼一個最簡單的方式可能以下: 網絡
Java代碼
- public class Task implements Runnable {
-
- @Override
- public void run() throws Exception {
- int number0 = Integer.parseInt("1");
- throw new Exception("Just for test");
- }
- }
但是,若是咱們去編譯上面這段代碼,會發現根本就編譯不過去的。系統報的錯誤是: 併發
Java代碼
- Task.java:3: error: run() in Task cannot implement run() in Runnable
- public void run() throws Exception {
- ^
- overridden method does not throw Exception
- 1 error
由此咱們發現這種方式行不通。也就是說,在線程裏直接拋異常是不行的。但是,這又會引出一個問題,若是咱們在線程代碼裏頭確實是產生了異常,那該怎麼辦 呢?好比說,咱們經過一個線程訪問一些文件或者對網絡進行IO操做,結果產生了異常。或者說訪問某些資源的時候系統崩潰了。這樣的場景是確實可能會發生 的,咱們就須要針對這些狀況進行進一步的討論。 ide
異常處理的幾種方式
在前面提到的幾種在線程訪問資源產生了異常的狀況。咱們能夠看,好比說咱們訪問文件系統的時候,會拋出IOException, FileNotFoundException等異常。咱們在訪問的代碼裏其實是須要採用兩種方式來處理的。一種是在使用改資源的方法頭增長throws IOException, FileNotFoundException等異常的修飾。還有一種是直接在這部分的代碼塊增長try/catch部分。由前面咱們的討論已經發現,在方 法聲明加throws Exception的方式是行不通的。那麼就只有使用try/catch這麼一種方式了。 this
另外,咱們也知道,在異常的處理上,通常異常能夠分爲checked exception和unchecked exception。做爲unchecked exception,他們一般是指一些比較嚴重的系統錯誤或者系統設計錯誤,好比Error, OutOfMemoryError或者系統直接就崩潰了。對於這種異常發生的時候,咱們通常是無能爲力也無法恢復的。那麼這種狀況的發生,咱們會怎麼來處 理呢? spa
checked exception
在線程裏面處理checked exception,按照咱們之前的理解,咱們是能夠直接捕捉它來處理的。在一些thread的示例裏咱們也見過。好比說下面的一部分代碼: 線程
Java代碼
- import java.util.Date;
- import java.util.concurrent.TimeUnit;
-
- public class FileLock implements Runnable {
- @Override
- public void run() {
- for(int i = 0; i < 10; i++) {
- System.out.printf("%s\n", new Date());
- try {
- TimeUnit.SECONDS.sleep(1);
- } catch(InterruptedException e) {
- System.out.printf("The FileClock has been interrupted");
- }
- }
- }
- }
咱們定義了一個線程執行代碼,而且在這裏由於調用TimeUnit.SECONDS.sleep()方法而須要捕捉異常。由於這個方法自己就會拋出InterruptedException,咱們必需要用try/catch塊來處理。 設計
咱們啓動該線程並和它交互的代碼以下: orm
Java代碼
- import java.util.concurrent.TimeUnit;
-
- public class Main {
- public static void main(String[] args) {
- // Creates a FileClock runnable object and a Thread
- // to run it
- FileClock clock=new FileClock();
- Thread thread=new Thread(clock);
-
- // Starts the Thread
- thread.start();
- try {
- // Waits five seconds
- TimeUnit.SECONDS.sleep(5);
- } catch (InterruptedException e) {
- e.printStackTrace();
- };
- // Interrupts the Thread
- thread.interrupt();
- }
- }
這部分的代碼是啓動FileLock線程並嘗試去中斷它。咱們能夠發如今運行的時候FileLock裏面執行的代碼可以正常的處理異常。
所以,在thread裏面,若是要處理checked exception,簡單的一個try/catch塊就能夠了。
unchecked exception
對於這種unchecked exception,相對來講就會不同一點。實際上,在Thread的定義裏有一個實例方 法:setUncaughtExceptionHandler(UncaughtExceptionHandler). 這個方法能夠用來處理一些unchecked exception。那麼,這種狀況的場景是如何的呢?
setUncaughtExceptionHandler()方法至關於一個事件註冊的入口。在jdk裏面,該方法的定義以下:
Java代碼
- public void setUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
- checkAccess();
- uncaughtExceptionHandler = eh;
- }
而UncaughtExceptionHandler則是一個接口,它的聲明以下:
Java代碼
- public interface UncaughtExceptionHandler {
- /**
- * Method invoked when the given thread terminates due to the
- * given uncaught exception.
- * <p>Any exception thrown by this method will be ignored by the
- * Java Virtual Machine.
- * @param t the thread
- * @param e the exception
- */
- void uncaughtException(Thread t, Throwable e);
- }
在異常發生的時候,咱們傳入的UncaughtExceptionHandler參數的uncaughtException方法會被調用。
綜合前面的討論,咱們這邊要實現handle unchecked exception的方法的具體步驟能夠總結以下:
1. 定義一個類實現UncaughtExceptionHandler接口。在實現的方法裏包含對異常處理的邏輯和步驟。
2. 定義線程執行結構和邏輯。這一步和普通線程定義同樣。
3. 在建立和執行改子線程的方法裏在thread.start()語句前增長一個thread.setUncaughtExceptionHandler語句來實現處理邏輯的註冊。
下面,咱們就按照這裏定義的步驟來實現一個示例:
首先是實現UncaughtExceptionHandler接口部分:
Java代碼
- import java.lang.Thread.UncaughtExceptionHandler;
-
- public class ExceptionHandler implements UncaughtExceptionHandler {
- public void uncaughtException(Thread t, Throwable e) {
- System.out.printf("An exception has been captured\n");
- System.out.printf("Thread: %s\n", t.getId());
- System.out.printf("Exception: %s: %s\n",
- e.getClass().getName(), e.getMessage());
- System.out.printf("Stack Trace: \n");
- e.printStackTrace(System.out);
- System.out.printf("Thread status: %s\n", t.getState());
- }
- }
這裏咱們添加的異常處理邏輯很簡單,只是把線程的信息和異常信息都打印出來。
而後,咱們定義線程的內容,這裏,咱們故意讓該線程產生一個unchecked exception:
Java代碼
- public class Task implements Runnable {
-
- @Override
- public void run() {
- int number0 = Integer.parseInt("TTT");
- }
- }
從這代碼裏咱們能夠看到,Integer.parseInt()裏面的參數是錯誤的,確定會拋出一個異常來。
如今,咱們再把建立線程和註冊處理邏輯的部分補上來:
Java代碼
- public class Main {
- public static void main(String[] args) {
- Task task = new Task();
- Thread thread = new Thread(task);
- thread.setUncaughtExceptionHandler(new ExceptionHandler());
- thread.start();
- }
- }
如今咱們去執行整個程序,會發現有以下的結果:
Java代碼
- An exception has been captured
- Thread: 8
- Exception: java.lang.NumberFormatException: For input string: "TTT"
- Stack Trace:
- java.lang.NumberFormatException: For input string: "TTT"
- at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
- at java.lang.Integer.parseInt(Integer.java:492)
- at java.lang.Integer.parseInt(Integer.java:527)
- at Task.run(Task.java:5)
- at java.lang.Thread.run(Thread.java:722)
- Thread status: RUNNABLE
這部分的輸出正好就是咱們前面實現UncaughtExceptionHandler接口的定義。
所以,對於unchecked exception,咱們也能夠採用相似事件註冊的機制作必定程度的處理。
總結
Java thread裏面關於異常的部分比較奇特。你不能直接在一個線程裏去拋出異常。通常在線程裏碰到checked exception,推薦的作法是採用try/catch塊來處理。而對於unchecked exception,比較合理的方式是註冊一個實現UncaughtExceptionHandler接口的對象實例來處理。這些細節的東西若是沒有碰到 過確實很難回答。