1. 程式人生 > >java捕獲執行緒中的異常

java捕獲執行緒中的異常

Java中在處理異常的時候,通常的做法是使用try-catch-finally來包含程式碼塊,但是Java自身還有一種方式可以處理——使用UncaughtExceptionHandler。它能檢測出某個執行緒由於未捕獲的異常而終結的情況。當一個執行緒由於未捕獲異常而退出時,JVM會把這個事件報告給應用程式提供的UncaughtExceptionHandler異常處理器(這是Thread類中的介面):

//Thread類中的介面
public interface UncaughtExceptionHanlder {
    void uncaughtException(Thread t, Throwable e);
}

JDK5之後允許我們在每一個Thread物件上新增一個異常處理器UncaughtExceptionHandler 。Thread.UncaughtExceptionHandler.uncaughtException()方法會線上程因未捕獲的異常而面臨死亡時被呼叫。

首先要先定義一個異常捕獲器:

public class MyUnchecckedExceptionhandler implements UncaughtExceptionHandler {
    @Override
    public void uncaughtException(Thread t, Throwable e) {
        System.out.println("捕獲異常處理方法:" + e);
    }
}


方法1. 建立執行緒時設定異常處理Handler

Thread t = new Thread(new ExceptionThread());
t.setUncaughtExceptionHandler(new MyUnchecckedExceptionhandler());
t.start();

方法2. 使用Executors建立執行緒時,還可以在TreadFactory中設定

ExecutorService exec = Executors.newCachedThreadPool(new ThreadFactory(){
            @Override
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(r);
                thread.setUncaughtExceptionHandler(new MyUnchecckedExceptionhandler());
                return thread;
            }
});
exec.execute(new ExceptionThread());

不過,上面的結果能證明:通過execute方式提交的任務,能將它丟擲的異常交給異常處理器。如果改成submit方式提交任務,則異常不能被異常處理器捕獲,這是為什麼呢?檢視原始碼後可以發現,如果一個由submit提交的任務由於丟擲了異常而結束,那麼這個異常將被Future.get封裝在ExecutionException中重新丟擲。所以,通過submit提交到執行緒池的任務,無論是丟擲的未檢查異常還是已檢查異常,都將被認為是任務返回狀態的一部分,因此不會交由異常處理器來處理。

java.util.concurrent.FutureTask 原始碼

public V get() throws InterruptedException, ExecutionException {
    int s = state;
    if (s <= COMPLETING)//如果任務沒有結束,則等待結束
        s = awaitDone(false, 0L);
    return report(s);//如果執行結束,則報告執行結果
}

@SuppressWarnings("unchecked")
private V report(int s) throws ExecutionException {
    Object x = outcome;
    if (s == NORMAL)//如果執行正常,則返回結果
        return (V)x;
    if (s >= CANCELLED)//如果任務被取消,呼叫get則報CancellationException
        throw new CancellationException();
    throw new ExecutionException((Throwable)x);//執行異常,則丟擲ExecutionException
}


方法3. 使用執行緒組ThreadGroup

//1.建立執行緒組
ThreadGroup threadGroup =
        // 這是匿名類寫法
        new ThreadGroup("group") {
            // 繼承ThreadGroup並重新定義以下方法
            // 線上程成員丟擲unchecked exception 會執行此方法
            @Override
            public void uncaughtException(Thread t, Throwable e) {
                //4.處理捕獲的執行緒異常
            }
        };
//2.建立Thread        
Thread thread = new Thread(threadGroup, new Runnable() {
    @Override
    public void run() {
        System.out.println(1 / 0);

    }
}, "my_thread");  
//3.啟動執行緒
thread.start();  

方法4. 預設的執行緒異常捕獲器


如果我們只需要一個執行緒異常處理器處理執行緒的異常,那麼我們可以設定一個預設的執行緒異常處理器,當執行緒出現異常時, 
如果我們沒有指定執行緒的異常處理器,而且執行緒組也沒有設定,那麼就會使用預設的執行緒異常處理器

// 設定預設的執行緒異常捕獲處理器
Thread.setDefaultUncaughtExceptionHandler(new MyUnchecckedExceptionhandler());

上面說的4種方法都是基於執行緒異常處理器實現的,接下來將的幾種方法則不需要依賴異常處理器。

方法5. 使用FetureTask來捕獲異常

//1.建立FeatureTask
FutureTask<Integer> futureTask = new FutureTask<>(new Callable<Integer>() {
    @Override
    public Integer call() throws Exception {
        return 1/0;
    }
});
//2.建立Thread
Thread thread = new Thread(future);
//3.啟動執行緒
thread.start();
try {
    Integer result = futureTask.get();
} catch (InterruptedException e) {
    e.printStackTrace();
} catch (ExecutionException e) {
    //4.處理捕獲的執行緒異常
}

方法6.利用執行緒池提交執行緒時返回的Feature引用

//1.建立執行緒池
ExecutorService executorService = Executors.newFixedThreadPool(10);
//2.建立Callable,有返回值的,你也可以建立一個執行緒實現Callable介面。
//  如果你不需要返回值,這裡也可以建立一個Thread即可,在第3步時submit這個thread。
Callable<Integer> callable = new Callable<Integer>() {
    @Override
    public Integer call() throws Exception {
        return 1/0;
    }
};
//3.提交待執行的執行緒
Future<Integer> future = executorService.submit(callable);
try {
     Integer result = future.get();
} catch (InterruptedException e) {
    e.printStackTrace();
} catch (ExecutionException e) {
    //4.處理捕獲的執行緒異常
}


實現原理可以看一下方法2的說明。 
方法6本質上和方法5一樣是基於FutureTask實現的。

總結


執行緒最好交由執行緒池進行管理。
執行緒池中如果你的執行緒不需要返回值則可以使用方法2,利用TreadFactory為執行緒指定統一的異常處理器。記得一定要用execute方式提交執行緒,否則異常處理器捕獲不到異常。
執行緒池中如果你的執行緒需要返回值,你又想捕獲執行緒異常,則需要藉助FeatureTask,即使用方法6。使用submit方法提交執行緒。當然了,不需要返回值的情況也可以使用方法6。