1 前言
我们平时在 Java 中处理异常的时候,通常的做法是使用try-catch-finally来包含代码块,但是Java自身还有一种方式可以处理就是使用UncaughtExceptionHandler,本节我们就来看看。
2 UncaughtExceptionHandler
2.1 认识
当 JVM 检测出某个线程由于未捕获的异常而终结的情况时,JVM会把这个事件报告给应用程序提供的UncaughtExceptionHandler异常处理器。这个是 JDK1.5 开始出现的,它是位于 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); }
可以通过以下实例方法来为每个线程设置一个UncaughtExceptionHandler:
// 给某个线程单独设置异常处理器 thread.setUncaughtExceptionHandler(UncaughtExceptionHandler handler);
或者通过以下静态方法来设置全局默认的UncaughtExceptionHandler:
// 全局的默认异常处理器 Thread.setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler handler);
这些异常处理器中,只有一个将会被调用——JVM首先搜索每个线程的异常处理器,若没有,则搜索该线程的ThreadGroup的异常处理器。ThreadGroup中的默认异常处理器实现是将处理工作逐层委托给上层的ThreadGroup,直到某个ThreadGroup的异常处理器能够处理该异常,否则一直传递到顶层的ThreadGroup。顶层ThreadGroup的异常处理器委托给默认的系统处理器(如果默认的处理器存在,默认情况下为空),否则把栈信息输出到System.err。
2.2 简单实践
下面我们来尝试一个:
/*** @author: kuku* @description*/ public class UncaughtTest {public static void main(String[] args) {// 创建一个线程Thread thread = new Thread(() -> {// 这里直接抛一个错throw new RuntimeException("出错了");});// 设置它的异常处理器thread.setUncaughtExceptionHandler((Thread t, Throwable e) -> {System.out.println(String.format("%s:发生了异常,e=%s", t.getName(), e.getMessage()));});// 启动线程 thread.start();} }
可以看到异常被异常处理器进行处理了:
我们再来个全局默认的和单独自己的:
/*** @author: kuku* @description*/ public class UncaughtTest {public static void main(String[] args) {Thread.setDefaultUncaughtExceptionHandler((Thread t, Throwable e) -> {System.out.println(String.format("我是全局默认的处理器,t=%s,e=%s", t.getName(), e.getMessage()));});// 创建一个线程Thread thread = new Thread(() -> {// 这里直接抛一个错throw new RuntimeException("出错了");});// 设置它自己的异常处理器thread.setUncaughtExceptionHandler((Thread t, Throwable e) -> {System.out.println(String.format("自己的异常处理器%s:发生了异常,e=%s", t.getName(), e.getMessage()));});// 启动线程 thread.start();// main 线程也抛一个异常throw new RuntimeException("main抛错");} }
main 线程没有自己的异常处理器,就走线程默认的,Thread-0有自己的就用自己的(就近原则):
2.3 搭配线程池
我们平时使用线程基本都是线程池,那么它怎么跟我们的线程池搭配使用呢?就在我们线程池的 ThreadFactory里可以引进:
/*** @author: kuku* @description*/ public class UncaughtTest {// 创建一个线程池private static final ThreadPoolExecutor POOL_EXECUTOR = new ThreadPoolExecutor(10,10,60,TimeUnit.SECONDS,new ArrayBlockingQueue<>(1000),new MyThreadFactory((Thread t, Throwable e) ->{System.out.println(String.format("%s:发生了异常,e=%s", t.getName(), e.getMessage()));}));private static class MyThreadFactory implements ThreadFactory {private Thread.UncaughtExceptionHandler uncaughtExceptionHandler;MyThreadFactory(Thread.UncaughtExceptionHandler uncaughtExceptionHandler) {this.uncaughtExceptionHandler = uncaughtExceptionHandler;}@Overridepublic Thread newThread(Runnable r) {Thread thread = new Thread(r);// 设置异常处理器 thread.setUncaughtExceptionHandler(uncaughtExceptionHandler);return thread;}}public static void main(String[] args) {POOL_EXECUTOR.execute(() -> {throw new RuntimeException("1");});POOL_EXECUTOR.execute(() -> {throw new RuntimeException("2");});POOL_EXECUTOR.execute(() -> {throw new RuntimeException("3");});POOL_EXECUTOR.execute(() -> {throw new RuntimeException("4");});} }
看效果:
这里线程池执行任务,我用的 execute,那当我换成 submit 还会达到同样的效果么?
public static void main(String[] args) throws ExecutionException, InterruptedException {Future<Object> one = POOL_EXECUTOR.submit(() -> {throw new RuntimeException("1");});System.out.println(one.get()); }我们看执行结果告诉我们,get的时候遇到了ExecutionException,并且也没走我们的默认异常处理器。
并且当我们不 get() 获取结果的时候,什么报错也不会有,这是为什么?我们来看看:
我们首先看看 submit 方法:
public <T> Future<T> submit(Callable<T> task) {if (task == null) throw new NullPointerException();// 把我们的task 封装成了 FutureTaskRunnableFuture<T> ftask = newTaskFor(task);// 执行 execute(ftask);return ftask; }那我们再看看 FutureTask 的 run 方法:
public void run() { ...try {Callable<V> c = callable;if (c != null && state == NEW) {V result;boolean ran;try {result = c.call();ran = true;// 我们抛出的异常 会被这里吞掉} catch (Throwable ex) {result = null;ran = false;setException(ex);} ... }protected void setException(Throwable t) {if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {outcome = t;UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state finishCompletion();} }继续再来看看Future.get方法的实现:
public V get() throws InterruptedException, ExecutionException {int s = state;// 如果任务没有结束,则等待结束if (s <= COMPLETING)s = awaitDone(false, 0L);// 如果执行结束,则报告执行结果return report(s); }@SuppressWarnings("unchecked") private V report(int s) throws ExecutionException {Object x = outcome;// 如果执行正常,则返回结果if (s == NORMAL)return (V)x;// 如果任务被取消,调用get则报CancellationExceptionif (s >= CANCELLED)throw new CancellationException();// 执行异常,则抛出ExecutionExceptionthrow new ExecutionException((Throwable)x); }在执行任务出现异常之后,异常存到了一个 outcome 字段中,只有在调用 get 方法获取 FutureTask 结果的时候,才会以 ExecutionException 的形式重新抛出异常。
如果一个由submit提交的任务由于抛出了异常而结束,那么这个异常将被Future.get封装在ExecutionException中重新抛出。所以,通过submit提交到线程池的任务,无论是抛出的未检查异常还是已检查异常,都将被认为是任务返回状态的一部分,因此不会交由异常处理器来处理。
那么我们上边的疑惑:
(1)当线程发生异常的时候,为什么不走默认的异常处理器? 因为 FutureTask 把我们抛出的异常捕获掉了,所以线程就不会异常,就走不到我们设置的异常处理器
(2)当调用 get() 获取的结果,自然就是根据线程实际执行的结果,如果有执行异常,会以ExecutionException 的形式重新抛出异常,跟我们的异常处理器搭不上关系了。
所以要记住 submit 和 直接 execute 的差异。
3 小结
好啦,本节关于线程的异常处理器就看到这里,有理解不对的地方欢迎指正哈。