前言
启动一个Java程序,本质上是运行某个Java类的main方法。我们写一个死循环程序,跑起来,然后运行 jvisualvm
进行观察
可以看到这个Java进程中,一共有11个线程,其中10个守护线程,1个用户线程。我们main方法中的代码,就跑在一个名为 main
的线程中。 当Java进程中跑着的所有线程都是守护线程时,JVM就会退出 。
在单线程的场景下,如果代码运行到某个位置时抛出了异常,会看到控制台打印出异常的堆栈信息。
但在多线程的场景下,子线程中发生的异常,不一定就能及时的将异常信息打印出来。
我曾经在工作中遇到过一次,采用 CompletableFuture.runAsync
异步处理耗时任务时,任务处理过程中出现异常,然而日志中没有任何关于异常的信息。
时隔许久,重新温习了线程中的异常处理机制,加深了对线程工作原理的理解,特此记录。
线程的异常处理机制
我们知道,Java程序的运行,是先经由 javac
将Java源代码编译成class字节码文件,然后由JVM加载并解析class文件,随后从主类的main方法开始执行。
当一个线程在运行过程中抛出了 未捕获异常 时,会由JVM调用这个线程对象上的 dispatchUncaughtException
方法,进行异常处理。
// Thread类中 private void dispatchUncaughtException(Throwable e) { getUncaughtExceptionHandler().uncaughtException(this, e); }
源码很好理解,先获取一个 UncaughtExceptionHandler
异常处理器,然后通过调用这个异常处理器的 uncaughtException
方法来对异常进行处理。(下文用缩写 ueh
来表示 UncaughtExceptionHandler
)
ueh
是个 啥呢?其实就是定义在 Thread
内部的一个接口,用作异常处理。
@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); }
再来看下 Thread
对象中的 getUncaughtExceptionHandler
方法
public UncaughtExceptionHandler getUncaughtExceptionHandler() { return uncaughtExceptionHandler != null ? uncaughtExceptionHandler : group; }
先查看当前这个 Thread
对象是否有设置自定义的 ueh
对象,若有,则由其对异常进行处理,否则,由当前 Thread
对象所属的线程组( ThreadGroup
)进行异常处理。我们点开源码,容易发现 ThreadGroup
类本身实现了 Thread.UncaughtExceptionHandler
接口,也就是说 ThreadGroup
本身就是个异常处理器。
public class ThreadGroup implements Thread.UncaughtExceptionHandler { private final ThreadGroup parent; .... }
假设我们在 main
方法中抛出一个异常,若没有对 main
线程设置自定义的 ueh
对象,则交由 main
线程所属的 ThreadGroup
来处理异常。我们看下 ThreadGroup
是怎么处理异常的:
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);