先看一个例子:
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
欢迎关注公众号