Java中線程執行的任務接口java.lang.Runnable 要求不拋出Checked異常,
public interface Runnable {
public abstract void run();
}
那么如果 run() 方法中拋出了RuntimeException,將會怎么處理了?
通常java.lang.Thread對象運行設置一個默認的異常處理方法:
java.lang.Thread.setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler)
而這個默認的靜態全局的異常捕獲方法時輸出堆棧。
當然,我們可以覆蓋此默認實現,只需要一個自定義的java.lang.Thread.UncaughtExceptionHandler接口實現即可。
public interface UncaughtExceptionHandler {
void uncaughtException(Thread t, Throwable e);
}
而在線程池中卻比較特殊。默認情況下,線程池 java.util.concurrent.ThreadPoolExecutor 會Catch住所有異常, 當任務執行完成(java.util.concurrent.ExecutorService.submit(Callable))獲取其結果 時(java.util.concurrent.Future.get())會拋出此RuntimeException。
/**
* Waits if necessary for the computation to complete, and then
* retrieves its result.
*
* @return the computed result
* @throws CancellationException if the computation was cancelled
* @throws ExecutionException if the computation threw an exception
* @throws InterruptedException if the current thread was interrupted while waiting
*/
V get() throws InterruptedException, ExecutionException;
其中 ExecutionException 異常即是java.lang.Runnable 或者 java.util.concurrent.Callable 拋出的異常。
也就是說,線程池在執行任務時捕獲了所有異常,并將此異常加入結果中。這樣一來線程池中的所有線程都將無法捕獲到拋出的異常。 從而無法通過設置線程的默認捕獲方法攔截的錯誤異常。
也不同通過自定義線程來完成異常的攔截。
好在java.util.concurrent.ThreadPoolExecutor 預留了一個方法,運行在任務執行完畢進行擴展(當然也預留一個protected方法beforeExecute(Thread t, Runnable r)):
protected void afterExecute(Runnable r, Throwable t) { }
此方法的默認實現為空,這樣我們就可以通過繼承或者覆蓋ThreadPoolExecutor 來達到自定義的錯誤處理。
解決辦法如下:
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(11, 100, 1, TimeUnit.MINUTES, //
new ArrayBlockingQueue<Runnable>(10000),//
new DefaultThreadFactory()) {
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
printException(r, t);
}
};
private static void printException(Runnable r, Throwable t) {
if (t == null && r instanceof Future<?>) {
try {
Future<?> future = (Future<?>) r;
if (future.isDone())
future.get();
} catch (CancellationException ce) {
t = ce;
} catch (ExecutionException ee) {
t = ee.getCause();
} catch (InterruptedException ie) {
Thread.currentThread().interrupt(); // ignore/reset
}
}
if (t != null)
log.error(t.getMessage(), t);
}
此辦法的關鍵在于,事實上 afterExecute 并不會總是拋出異常 Throwable t,通過查看源碼得知,異常是封裝在此時的Future對象中的, 而此Future對象其實是一個java.util.concurrent.FutureTask的實現,默認的run方法其實調用的 java.util.concurrent.FutureTask.Sync.innerRun()。
void innerRun() {
if (!compareAndSetState(0, RUNNING))
return;
try {
runner = Thread.currentThread();
if (getState() == RUNNING) // recheck after setting thread
innerSet(callable.call());
else
releaseShared(0); // cancel
} catch (Throwable ex) {
innerSetException(ex);
}
}
void innerSetException(Throwable t) {
for (;;) {
int s = getState();
if (s == RAN)
return;
if (s == CANCELLED) {
// aggressively release to set runner to null,
// in case we are racing with a cancel request
// that will try to interrupt runner
releaseShared(0);
return;
}
if (compareAndSetState(s, RAN)) {
exception = t;
result = null;
releaseShared(0);
done();
return;
}
}
}
這里我們可以看到它吃掉了異常,將異常存儲在java.util.concurrent.FutureTask.Sync的exception字段中:
/** The exception to throw from get() */
private Throwable exception;
當我們獲取異步執行的結果時, java.util.concurrent.FutureTask.get()
public V get() throws InterruptedException, ExecutionException {
return sync.innerGet();
}
java.util.concurrent.FutureTask.Sync.innerGet()
V innerGet() throws InterruptedException, ExecutionException {
acquireSharedInterruptibly(0);
if (getState() == CANCELLED)
throw new CancellationException();
if (exception != null)
throw new ExecutionException(exception);
return result;
}
異常就會被包裝成ExecutionException異常拋出。
也就是說當我們想線程池 ThreadPoolExecutor(java.util.concurrent.ExecutorService)提交任務時, 如果不理會任務結果(Feture.get()),那么此異常將被線程池吃掉。
<T> Future<T> submit(Callable<T> task);
Future<?> submit(Runnable task);
而java.util.concurrent.ScheduledThreadPoolExecutor是繼承ThreadPoolExecutor的,因此情況類似。
結論,通過覆蓋ThreadPoolExecutor.afterExecute 方法,我們才能捕獲到任務的異常(RuntimeException)。
原文地址:http://imxylz.com/blog/2013/08/02/handling-the-uncaught-exception-of-java-thread-pool/
©2009-2014 IMXYLZ
|求賢若渴