用Thread中的UncaughtExceptionHandler來處理未捕獲的異常

Java中在處理異常的時候,一般的作法是使用try-catch-finally來包含代碼塊,可是Java自身還有一種方式能夠處理——使用UncaughtExceptionHandler。它能檢測出某個線程因爲未捕獲的異常而終結的狀況。當一個線程因爲未捕獲異常而退出時,JVM會把這個事件報告給應用程序提供的UncaughtExceptionHandler異常處理器(這是Thread類中的接口):java

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

在Java 5之後,能夠經過如下實例方法來爲每一個線程設置一個UncaughtExceptionHandler:ide

Thread.setUncaughtExceptionHandler(UncaughtExceptionHandler handler);//實例方法

或者經過如下靜態方法來設置默認的UncaughtExceptionHandler:函數

Thread.setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler handler);//靜態方法

這些異常處理器中,只有一個將會被調用——JVM首先搜索每一個線程的異常處理器,若沒有,則搜索該線程的ThreadGroup的異常處理器。ThreadGroup中的默認異常處理器實現是將處理工做逐層委託給上層的ThreadGroup,直到某個ThreadGroup的異常處理器可以處理該異常,不然一直傳遞到頂層的ThreadGroup。頂層ThreadGroup的異常處理器委託給默認的系統處理器(若是默認的處理器存在,默認狀況下爲空),不然把棧信息輸出到System.err。下面是一個Example:this

import java.lang.Thread.UncaughtExceptionHandler;

public class T {
	public static void main(String[] args) throws Exception {
		//爲全部線程設置默認的未捕捉異常處理器
		Thread.setDefaultUncaughtExceptionHandler(new MyDefaultExceptionHandler());
		Thread.currentThread().setName("Main Thread");
		
		Thread thread = new Thread(new MyTask("MyTask"), "Child Thread");
		//爲某個線程單獨設置異常處理器
		thread.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
		thread.start();
		//主線程拋出異常,將會使用默認的異常處理器
		throw new RuntimeException("IllegalArgumentException");
	}
}

class MyDefaultExceptionHandler implements UncaughtExceptionHandler {
	@Override
	public void uncaughtException(Thread t, Throwable e) {
		System.out.println("MyDefaultExceptionHandler: Thread: " + 
				t.getName() + ", Message: " + e.getMessage());
	}
}
class MyUncaughtExceptionHandler implements UncaughtExceptionHandler {
	@Override
	public void uncaughtException(Thread t, Throwable e) {
		System.out.println("MyUncaughtExceptionHandler: Thread: " + 
				t.getName() + ", Message: " + e.getMessage());
	}
}
class MyTask implements Runnable {
	private String name;
	public MyTask(String name) {
		this.name = name;
	}
	public MyTask(){}
	public String getName() {
		return name;
	}
	@Override
	public void run() {
		throw new RuntimeException(name + " gets a NullPointerException");
	}
}

執行結果:spa

MyDefaultExceptionHandler: Thread: Main Thread, Message: IllegalArgumentException
MyUncaughtExceptionHandler: Thread: Child Thread, Message: MyTask gets a NullPointerException

能夠看到,Main Thread因爲沒有顯式設置UncaughtExceptionHandler,其拋出的未捕獲異常,被默認異常處理器MyDefaultUncaughtExceptionHandler處理了,而Child Thread因爲單獨設置了UncaughtExceptionHanlder,其拋出的未捕獲異常,則被Thread自己自帶的MyUncaughtExceptionHandler處理了。線程

若是要爲線程池中的全部線程設置一個UncaughtExceptionHandler,則須要爲ThreadPoolExecutor的構造函數提供一個自定義的ThreadFactory(與全部的線程操控同樣,只有線程的全部者可以改變線程的UncaughtExceptionHandler):code

import java.lang.Thread.UncaughtExceptionHandler;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;

public class T {
	public static void main(String[] args) throws Exception {
		//使用自定義的ThreadFactory來建立線程,並綁定同一個異常處理器
		UncaughtExceptionHandler handler = new MyUncaughtExceptionHandler();
		ExecutorService executor = Executors.newCachedThreadPool(new MyThreadFactory(handler));
		executor.execute(new MyTask("task1"));
		executor.execute(new MyTask("task2"));
		executor.execute(new MyTask("task3"));
		executor.shutdown();
	}
}
class MyTask implements Runnable {
	private String name;
	public MyTask(String name) {
		this.name = name;
	}
	public MyTask(){}
	public String getName() {
		return name;
	}
	@Override
	public void run() {
		throw new RuntimeException(name + " gets a NullPointerException");
	}
}
class MyUncaughtExceptionHandler implements UncaughtExceptionHandler {
	@Override
	public void uncaughtException(Thread t, Throwable e) {
		System.out.println("MyUncaughtExceptionHandler: Thread: " + 
				t.getName() + ", Message: " + e.getMessage());
	}
}
class MyThreadFactory implements ThreadFactory {
	private UncaughtExceptionHandler handler;
	public MyThreadFactory(UncaughtExceptionHandler handler) {
		this.handler = handler;
	}
	@Override
	public Thread newThread(Runnable r) {
		Thread thread = new Thread(r);
		//在這裏設置異常處理器
		thread.setUncaughtExceptionHandler(handler);
		return thread;
	}
}

執行結果:接口

MyUncaughtExceptionHandler: Thread: Thread-0, Message: task1 gets a NullPointerException
MyUncaughtExceptionHandler: Thread: Thread-2, Message: task3 gets a NullPointerException
MyUncaughtExceptionHandler: Thread: Thread-1, Message: task2 gets a NullPointerException

從結果中能夠看出,線程池中的每一個線程都使用同一個異常處理器來處理未捕獲的異常。事件

不過,上面的結果能證實:經過execute提交的任務,能將它拋出的異常交給未捕獲的異常處理器。下面的例子只修改了main方法(其他部分請參考前文),以submit方式提交任務:get

public class T {
	public static void main(String[] args) throws Exception {
		//使用自定義的ThreadFactory來建立線程,並綁定同一個異常處理器
		UncaughtExceptionHandler handler = new MyUncaughtExceptionHandler();
		ExecutorService executor = Executors.newCachedThreadPool(new MyThreadFactory(handler));
		//經過submit方法提交任務
		Future future1 = executor.submit(new MyTask("task1"));
		Future future2 = executor.submit(new MyTask("task2"));
		System.out.println(future1.get());
		System.out.println(future2.get());
		executor.shutdown();
	}
}

執行結果:

Exception in thread "main" java.util.concurrent.ExecutionException: 
java.lang.RuntimeException: task1 gets a NullPointerException
	at java.util.concurrent.FutureTask.report(FutureTask.java:122)
	at java.util.concurrent.FutureTask.get(FutureTask.java:188)
	at T.main(T.java:15)
Caused by: java.lang.RuntimeException: task1 gets a NullPointerException
	at MyTask.run(T.java:31)
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471)
	at java.util.concurrent.FutureTask.run(FutureTask.java:262)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
	at java.lang.Thread.run(Thread.java:745)

結果告訴咱們,future1.get的時候遇到了ExecutionException。那咱們再來看看Future.get方法的實現(在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
}

源代碼說明:若是一個由submit提交的任務因爲拋出了異常而結束,那麼這個異常將被Future.get封裝在ExecutionException中從新拋出。因此,經過submit提交到線程池的任務,不管是拋出的未檢查異常仍是已檢查異常,都將被認爲是任務返回狀態的一部分,所以不會交由異常處理器來處理。

相關文章
相關標籤/搜索