Java 线程 UncaughtExceptionHandler 异常处理机制

先看一个例子:

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)

可以看到线程无法捕获它的派生线程的异常。

分析

UncaughtExceptionHandlerThread 类的一个内部接口:

    /**
     * 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,会尝试调用 ThreadGroupUncaughtExceptionHandler,如果还是没有设置,那么会调用 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 就会使用 groupThreadGroup 本身就是 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());
	}

但是 AsyncUncaughtExceptionHandlerUncaughtExceptionHandler 的区别是,由于 Spring 是基于代理的方式去实现 @Async 异步调用的,所有它可以直接在 catch 中去处理异常,具体处理方式就再去调用配置的 AsyncUncaughtExceptionHandler

接下来再以 ZooKeeper 为例。在构建 Zookeeper 实例的时候会创建 ClientCnxn,同时会启动 ClientCnxn 内部的 SendThreadEventThread。他们都继承自 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

欢迎关注公众号
​​​​​​在这里插入图片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章