學過Java多線程的夥伴們都知道,Java入門的多線程方案就是Thread類和Runnable類。如下:
public class Demo {
public static void main(String[] args) {
fun1();
fun2();
}
public static void fun1() {
Thread thread = new Thread() {
@Override
public void run() {
System.out.println("fun1-thread");
}
};
thread.start();
}
public static void fun2() {
Thread thread = new Thread() {
@Override
public void run() {
System.out.println("fun2-thread");
}
};
thread.start();
}
}
這是Java的基礎內容,在此不再贅述。不知道的夥伴可以參見我之前的博客,今天我們要說的是線程內部的異常處理。
Java的異常非爲受檢異常和非受檢異常。由於在多線程中,run()方法無法繼續向上顯式拋出異常。所以這就使得Thread線程中的異常處理變得棘手。
首先對於受檢異常,其應對辦法非常通用:就是直接在run()方法體中捕獲異常,然後進行對應的處理。對於非受檢異常,JVM會幫助我們捕獲到。那麼我們如何處理JVM捕獲的異常呢?答案是Thread.UncaughtExceptionHandler類。正如JDK文檔所介紹的一樣:
“當一個線程由於發生了非受檢異常而終止時,JVM會使用Thread.gerUncaughtExceptionHandler()方法查看該線程上的UncaughtExceptionHandler,並調用他的uncaughtException()方法”——翻譯自JDK7 API文檔。
下面我們來嘗試使用hread.UncaughtExceptionHandler類,來處理線程內部的非受檢異常(受檢異常直接在run()方法體內部的catch子句中處理)。
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Created by yizhen on 2017/1/7.
*/
public class Demo {
private static final AtomicInteger num = new AtomicInteger();
public static void main(String[] args) throws InterruptedException {
final Thread thread = new Thread(new ThreadTask());
//此處使用thread.setUncaughtExceptionHandler(new ThreadExceptionHandler)可以代替ThreadTask中
//的Thread.currentThread().setUncaughtExceptionHandler(new ThreadExceptionHandler(),但這樣不好
thread.start();
}
private static final class ThreadExceptionHandler implements Thread.UncaughtExceptionHandler {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("One uncaught exception was got:");
System.out.println("Thread id=" + t.getId());
System.out.println("Thread name=" + t.getName());
e.printStackTrace(System.out);
// 當捕獲到非受檢異常時,可以斷定原來的線程已經被JVM終止。
// 此時可以新建一個線程繼續執行原來的任務
if (num.get() < 5) {
new Thread(new ThreadTask()).start();
}
}
}
private static final class ThreadTask implements Runnable {
@Override
public void run() {
num.incrementAndGet();
Thread.currentThread().setUncaughtExceptionHandler(new ThreadExceptionHandler());//設置非受檢異常的ExceptionHandler
try {
for (int i = 4; i >= 0; i--) {
TimeUnit.SECONDS.sleep(1);
// 當i=0時候拋出的非受檢異常,將導致當前線程被JVM殺死
// 但異常會被在上面設置的ThreadExceptionHandler捕獲到,進而被處理
System.out.println(12 / i);
}
} catch (InterruptedException e) {//受檢異常直接在run方法體內部處理
e.printStackTrace();
}
}
}
}
上面的代碼不難理解,在主函數中,我們執行了一個Thread線程,這個線程中執行ThreadTask任務。在ThreadTask任務的實現中,如果是受檢異常,直接在run()方法體中處理。對於非受檢異常,我們在run()方法體的開頭設置了非受檢異常處理類ThreadExceptionException,這個類的uncaughtException()方法就是處理線程內部非捕獲異常的具體執行者。該方法會判斷任務執行的次數,如果沒有超過5次,就會創建一個Thread線程來重新執行任務(讀者要清楚,一旦一個線程拋出了非受檢異常,JVM就會把它殺死,然後把捕獲到的非受檢異常傳遞給UncaughtExceptionHandler類對象類處理)。
一定有讀者會問,如果沒有通過setUncaughtExceptionHandler()方法設置Handler怎麼辦?這個問題在JDK API中給了回答:
“如果一個線程沒有顯式的設置它的UncaughtExceptionHandler,JVM就會檢查該線程所在的線程組是否設置了UncaughtExceptionHandler,如果已經設置,就是用該UncaughtExceptionHandler;否則查看是否在Thread層面通過靜態方法setDefaultUncaughtExceptionHandler()設置了UncaughtExceptionHandler,如果已經設置就是用該UncaughtExceptionHandler;如果上述都沒有找到,JVM會在對應的console中打印異常的堆棧信息。”——翻譯自JDK7 API文檔
/**
* Created by yizhen on 2017/1/7.
*/
public class Demo {
public static void main(String[] args) throws InterruptedException {
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("static thread exception handler -- " + t.getName());
}
});
final Thread t1 = new Thread(new ThreadTaskWithHandler(), "t1");
t1.start();
final Thread t2 = new Thread(new ThreadTaskNoHandler(), "t2");
t2.start();
}
private static final class ThreadExceptionHandler implements Thread.UncaughtExceptionHandler {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("explicit exception handler -- " + t.getName());
}
}
private static final class ThreadTaskWithHandler implements Runnable {
@Override
public void run() {
Thread.currentThread().setUncaughtExceptionHandler(new ThreadExceptionHandler());
System.out.println(12 / 0);
}
}
private static final class ThreadTaskNoHandler implements Runnable {
@Override
public void run() {
System.out.println(12 / 0);
}
}
}
從上面的程序可以看到,t1線程的非受檢異常始終會被explicit exception handler捕獲到,而t2線程的非受檢異常始終會被static thread exception handler捕獲到。
至此,Thread線程內的異常處理就介紹完了,這包括受檢異常和非受檢異常。細心的讀者會發現,本文僅僅涉及Thread和Runnable的多線程體系。在Java中,還有Executor和Callable的多線程體系。那關於這個體系的異常如何處理呢?筆者會後續博文中介紹!