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

歡迎關注公衆號
​​​​​​在這裏插入圖片描述

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