先看一個例子:
public static void main(String[] args) {
try {
new Thread(() -> {
int i = 1/0;
}).start();
}catch (Throwable e){
System.out.println("error...");
}
}
輸出結果:
Exception in thread "Thread-0" java.lang.ArithmeticException: / by zero
at com.dongguabai.java.crawler.Test.lambda$main$0(Test.java:14)
at java.base/java.lang.Thread.run(Thread.java:834)
可以看到線程無法捕獲它的派生線程的異常。
分析
UncaughtExceptionHandler
是 Thread
類的一個內部接口:
/**
* Interface for handlers invoked when a {@code Thread} abruptly
* terminates due to an uncaught exception.
* <p>When a thread is about to terminate due to an uncaught exception
* the Java Virtual Machine will query the thread for its
* {@code UncaughtExceptionHandler} using
* {@link #getUncaughtExceptionHandler} and will invoke the handler's
* {@code uncaughtException} method, passing the thread and the
* exception as arguments.
* If a thread has not had its {@code UncaughtExceptionHandler}
* explicitly set, then its {@code ThreadGroup} object acts as its
* {@code UncaughtExceptionHandler}. If the {@code ThreadGroup} object
* has no
* special requirements for dealing with the exception, it can forward
* the invocation to the {@linkplain #getDefaultUncaughtExceptionHandler
* default uncaught exception handler}.
*
* @see #setDefaultUncaughtExceptionHandler
* @see #setUncaughtExceptionHandler
* @see ThreadGroup#uncaughtException
* @since 1.5
*/
@FunctionalInterface
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
接口,通過 getUncaughtExceptionHandler
方法查看當前線程是否設置了 UncaughtExceptionHandler
。有就調用,由於線程在創建的時候都會屬於一個 ThreadGroup
,會嘗試調用 ThreadGroup
的 UncaughtExceptionHandler
,如果還是沒有設置,那麼會調用 getDefaultUncaughtExceptionHandler
獲取全局默認的 UncaughtExceptionHandler
。
具體是使用 java.lang.Thread#dispatchUncaughtException
進行回調:
/**
* Dispatch an uncaught exception to the handler. This method is
* intended to be called only by the JVM.
*/
private void dispatchUncaughtException(Throwable e) {
getUncaughtExceptionHandler().uncaughtException(this, e);
}
再看 java.lang.Thread#getUncaughtExceptionHandler
方法:
public UncaughtExceptionHandler getUncaughtExceptionHandler() {
return uncaughtExceptionHandler != null ?
uncaughtExceptionHandler : group;
}
如果當前線程本身沒有設置 uncaughtExceptionHandler
就會使用 group
,ThreadGroup
本身就是 UncaughtExceptionHandler
:
public class ThreadGroup implements Thread.UncaughtExceptionHandler {
public void uncaughtException(Thread t, Throwable e) {
if (parent != null) {
parent.uncaughtException(t, e);
} else {
Thread.UncaughtExceptionHandler ueh =
Thread.getDefaultUncaughtExceptionHandler();
if (ueh != null) {
ueh.uncaughtException(t, e);
} else if (!(e instanceof ThreadDeath)) {
System.err.print("Exception in thread \""
+ t.getName() + "\" ");
e.printStackTrace(System.err);
}
}
}
}
總結一些,在 Thread
中有兩種方式可以設置 UncaughtExceptionHandler
:
//設置全局 UncaughtExceptionHandler
public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh);
//爲當前 Thread 設置 UncaughtExceptionHandler
public void setUncaughtExceptionHandler(UncaughtExceptionHandler eh);
接下來將開頭那個示例改造一下:
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
int i = 1 / 0;
});
thread.setUncaughtExceptionHandler((t, e) -> System.out.printf("線程【%s】發生異常,異常信息:\n%s", t.getName(), Arrays.toString(e.getStackTrace())));
thread.start();
}
輸出:
線程【Thread-0】發生異常,異常信息:
[com.dongguabai.java.crawler.Test.lambda$main$0(Test.java:18), java.base/java.lang.Thread.run(Thread.java:834)]
在其他框架中的應用
UncaughtExceptionHandler
提供了一種回調處理異步線程執行失敗的思想。在 Spring 的 @Async
中可以通過配置 AsyncUncaughtExceptionHandler
在異步任務執行失敗的時候進行處理。
public Object invoke(final MethodInvocation invocation) throws Throwable {
Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
Method specificMethod = ClassUtils.getMostSpecificMethod(invocation.getMethod(), targetClass);
final Method userDeclaredMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
AsyncTaskExecutor executor = determineAsyncExecutor(userDeclaredMethod);
if (executor == null) {
throw new IllegalStateException(
"No executor specified and no default executor set on AsyncExecutionInterceptor either");
}
Callable<Object> task = () -> {
try {
Object result = invocation.proceed();
if (result instanceof Future) {
return ((Future<?>) result).get();
}
}
catch (ExecutionException ex) {
handleError(ex.getCause(), userDeclaredMethod, invocation.getArguments());
}
catch (Throwable ex) {
handleError(ex, userDeclaredMethod, invocation.getArguments());
}
return null;
};
return doSubmit(task, executor, invocation.getMethod().getReturnType());
}
但是 AsyncUncaughtExceptionHandler
與 UncaughtExceptionHandler
的區別是,由於 Spring 是基於代理的方式去實現 @Async
異步調用的,所有它可以直接在 catch 中去處理異常,具體處理方式就再去調用配置的 AsyncUncaughtExceptionHandler
。
接下來再以 ZooKeeper 爲例。在構建 Zookeeper
實例的時候會創建 ClientCnxn
,同時會啓動 ClientCnxn
內部的 SendThread
和 EventThread
。他們都繼承自 ZooKeeperThread
:
public class ZooKeeperThread extends Thread {
private static final Logger LOG = LoggerFactory
.getLogger(ZooKeeperThread.class);
private UncaughtExceptionHandler uncaughtExceptionalHandler = new UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
handleException(t.getName(), e);
}
};
public ZooKeeperThread(Runnable thread, String threadName) {
super(thread, threadName);
setUncaughtExceptionHandler(uncaughtExceptionalHandler);
}
protected void handleException(String thName, Throwable e) {
LOG.warn("Exception occurred from thread {}", thName, e);
}
}
而 handleException
方法可以交由子類去擴展,處理特定的業務邏輯。
Spring 和 ZooKeeper 的思想是可以借鑑的,我們可以通過代理配置異常處理器的方式,也可以設計一個頂層的 Thread
基類,基於模版的方式去處理特定的異步異常。
References
- https://blog.csdn.net/Dongguabai/article/details/82986476
歡迎關注公衆號