欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Hook线程以及捕获线程执行异常

程序员文章站 2022-05-04 10:13:22
...

1、获取线程运行时异常

1.1、在Thread类中,关于处理运行时异常的API总共有四个,如下所示:

 public static void setUncaughtExceptionHandler(UncaughtExceptionHandler eh) :为某个特定线程指定UncaughtExceptionHandler

 public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh) :设置全局的UncaughtExceptionHandler

 public static void getUncaughtExceptionHandler(UncaughtExceptionHandler eh) :获取特定线程的UncaughtExceptionHandler

 public static void getDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh):获取全局的UncaughtExceptionHandler

1.2、UncaughtExceptionHandler的介绍

线程在执行单元中时不允许抛出checked异常的,而且线程运行在自己的上下文中,派生他的线程将无法直接获得它运行中出现的异常信息。对此,

Java为我们提供了一个UncaughtExceptionHandler接口,当线程在运行过程中出现异常时,会回调UncaughtExceptionHandler接口,从而得知时那个线程在

运行时出错,以及出现了什么样的错误,源码如下:

    @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时一个FunctionInterface,只有一个抽象方法,该回调接口会被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);
    }

 当线程在运行过程中出现异常时,JVM会调用dispatchUncaughtException方法,该方法会将对应的线程实例以及异常信息传递给回调接口。

1.3 UncaughtExceptionHandler实例

public class CaptureThreadException {
    public static void main(String[] args) {
        //设置全局异常回调接口
        Thread.setDefaultUncaughtExceptionHandler((t,e)->{
            System.out.println(t.getName()+" occur exception");
            e.printStackTrace();
        });
        final Thread thread=new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
            }
            //here will throw unchecked exception
            System.out.println(1/0);
        },"Test-Thread");
        thread.start();
    }
}

执行上面的程序,线程Test-Thread在运行两秒之后会抛出一个unchecked异常,我们设置的回调接口将获得该异常信息,程序的执行结果如下:

Hook线程以及捕获线程执行异常

1.4 UncaughtExceptionHandler源码分析

在没有向线程注入UncaughtExceptionHandler回调接口的情况下,线程若出现了异常又将如何处理呢?下面我们将通过对Thread的源码进行分析来追踪一下,示例代码如下:

   /**
     * Returns the handler invoked when this thread abruptly terminates
     * due to an uncaught exception. If this thread has not had an
     * uncaught exception handler explicitly set then this thread's
     * <tt>ThreadGroup</tt> object is returned, unless this thread
     * has terminated, in which case <tt>null</tt> is returned.
     * @since 1.5
     * @return the uncaught exception handler for this thread
     */
    public UncaughtExceptionHandler getUncaughtExceptionHandler() {
        return uncaughtExceptionHandler != null ?
            uncaughtExceptionHandler : group;
    }

getUncaughtExceptionHandler方法首先会判断当前线程是否设置了handler,如果有则执行线程自己的uncaughtException方法,否则就到所在的ThreadGroup中获取,ThreadGroup同样也实现了UncaughtExceptionHandler接口,下面再来看看ThreadGroup的uncaughtException方法:

/**
     * Called by the Java Virtual Machine when a thread in this
     * thread group stops because of an uncaught exception, and the thread
     * does not have a specific {@link Thread.UncaughtExceptionHandler}
     * installed.
     * <p>
     * The <code>uncaughtException</code> method of
     * <code>ThreadGroup</code> does the following:
     * <ul>
     * <li>If this thread group has a parent thread group, the
     *     <code>uncaughtException</code> method of that parent is called
     *     with the same two arguments.
     * <li>Otherwise, this method checks to see if there is a
     *     {@linkplain Thread#getDefaultUncaughtExceptionHandler default
     *     uncaught exception handler} installed, and if so, its
     *     <code>uncaughtException</code> method is called with the same
     *     two arguments.
     * <li>Otherwise, this method determines if the <code>Throwable</code>
     *     argument is an instance of {@link ThreadDeath}. If so, nothing
     *     special is done. Otherwise, a message containing the
     *     thread's name, as returned from the thread's {@link
     *     Thread#getName getName} method, and a stack backtrace,
     *     using the <code>Throwable</code>'s {@link
     *     Throwable#printStackTrace printStackTrace} method, is
     *     printed to the {@linkplain System#err standard error stream}.
     * </ul>
     * <p>
     * Applications can override this method in subclasses of
     * <code>ThreadGroup</code> to provide alternative handling of
     * uncaught exceptions.
     *
     * @param   t   the thread that is about to exit.
     * @param   e   the uncaught exception.
     * @since   JDK1.0
     */
    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);
            }
        }
    }

(1)该ThreadGroup如果有父ThreadGroup,则直接调用父Group的uncaughtException方法

(2)如果设置了全局默认的UncaughtExcpetionHandler,则调用uncaughtException方法

(3)若既没有父ThreadGroup,也没有设置全局默认的UncaughtExceptionHandler,则会直接将异常的堆栈信息定向到Systerm.err中

下面是没有设置默认的Handler,也没有对thread指定Handler,因此当thread出现异常时,会向上寻找Group的uncaughtException方法

public class EmptyExceptionHandler {
    public static void main(String[] args) {
        //get current thread's thread group
        ThreadGroup mainGroup = Thread.currentThread().getThreadGroup();
        System.out.println(mainGroup.getName());
        System.out.println(mainGroup.getParent());
        System.out.println(mainGroup.getParent().getParent());

        final Thread thread=new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
            }
            //here will throw unchecked exception
            System.out.println(1/0);
        },"Test-Thread");
        thread.start();
    }
}

执行结果:

Hook线程以及捕获线程执行异常

相关标签: Java高并发编程