线程池源码-异常处理
在线程池源码系列文章 线程池源码-线程被全部关闭了吗 中有提到,线程池在结束 worker 线程时会有一个标识 completedAbruptly,用来判断线程是否为异常退出。
那什么时候线程会异常退出呢?答案很明显,在执行任务的过程中抛出了异常,且没有进行 try catch 处理。
本篇文章主要探索下面的问题
- 线程池默认的异常处理方式,存在的问题
- 关于异常处理的解决方案
默认处理
想知道线程池对异常的默认处理方式,我们先来看看下面的测试代码:
public static void main(String[] args){ ExecutorService threadPool = Executors.newFixedThreadPool(5); for (int i = 0; i < 5; i++) { threadPool.execute(() -> { System.out.println("current thread: " + Thread.currentThread().getName()); int res = 1 / 0; }); } }
运行结果,可以看到,控制台打印出了异常的堆栈信息:
在 线程池源码-任务执行 中有提到任务执行的过程,直接看到相关的核心代码:
final void runWorker(Worker w) { Thread wt = Thread.currentThread(); Runnable task = w.firstTask; w.firstTask = null; w.unlock(); // allow interrupts boolean completedAbruptly = true; try { while (task != null || (task = getTask()) != null) { try { beforeExecute(wt, task); Throwable thrown = null; try { task.run(); } catch (RuntimeException x) { // 捕获异常并抛出 thrown = x; throw x; } catch (Error x) { thrown = x; throw x; } catch (Throwable x) { thrown = x; throw new Error(x); } finally { afterExecute(task, thrown); } } finally { task = null; w.completedTasks++; w.unlock(); } } completedAbruptly = false; } finally { processWorkerExit(w, completedAbruptly); } }
通过阅读任务执行的源码,发现它 catch 住异常之后还会继续抛出,最终使用了 jvm 默认的异常处理方式,在控制台打印堆栈的异常信息。
使用默认的处理方式,显然是不切合实际的,被你 leader 看到这样处理异常,岂不把你打屎???在很多场景下,我们需要捕获这个异常,不管是进行特殊处理,还是记录日志,方便后续排查问题。
解决方案
内部捕获
这是最直接简单的方法,在任务执行的过程中捕获可能发生的异常。但同时你无法预知程序运行过程中可能发生的所有异常,当然你也可以用一个超级无敌大的 try {} catch{} 把所有代码都包住,确保万无一失,这种写法极其不合理且丑陋,代码这边就不赘述了。
FutureTask
JDK 提供了另外一种更优雅的处理方式,线程池提交任务还可以使用 submit() 方法,这个方法可以返回执行结果,包括异常信息。
threadPool.submit(() -> { System.out.println("current thread: " + Thread.currentThread().getName()); Object object = null; // 此处会抛空指针异常 System.out.print(object.toString()); });
通过 submit() 提交任务, Runnable 对象被一步封装成了 FutureTask 对象, FutureTask 对异常进行了特殊处理,可以通过 futureTask.get() 来获取任务执行过程中抛出的异常,后续的文章会对FutureTask 进行较为详细地解析,这边暂不详细赘述。
public Future<?> submit(Runnable task) { if (task == null) throw new NullPointerException(); RunnableFuture<Void> ftask = newTaskFor(task, null); execute(ftask); return ftask; } protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) { return new FutureTask<T>(runnable, value); }
修改一下测试代码:
public static void main(String[] args){ ExecutorService threadPool = Executors.newFixedThreadPool(5); List<Future> futures = new ArrayList<>(); for (int i = 0; i < 5; i++) { Future future = threadPool.submit(() -> { System.out.println("current thread: " + Thread.currentThread().getName()); int res = 1 / 0; }); futures.add(future); } // 捕获异常 futures.forEach(future -> { try { future.get(); } catch (Exception e) { System.out.println("捕获异常信息:" + e.getMessage()); } }); }
执行结果如下,可以看到异常堆栈被打印出来:
FutureTask 确实能够捕获子线程执行过程中抛出的异常,**但它需要显式地去遍历 Future 结果集,通过 future.get() 去获取程序异常,**似乎也不是太优雅的做法。
线程池接口扩展
线程池在任务执行完毕之后,会调用 afterExecute() 方法,可以看到其将运行结果,以及捕获的异常都作为参数传入方法中。
try { beforeExecute(wt, task); Throwable thrown = null; try { task.run(); } catch (RuntimeException x) { // 捕获异常并抛出 thrown = x; throw x; } catch (Error x) { thrown = x; throw x; } catch (Throwable x) { thrown = x; throw new Error(x); } finally { // 任务执行完调用,无论是否发生异常 afterExecute(task, thrown); } } finally { task = null; w.completedTasks++; w.unlock(); }
而 afterExecute()方法默认是没有实现的,是线程池提供给外部的扩展接口。可以通过重写此方法,对异常进行统一处理。
class ExtendThreadPool extends ThreadPoolExecutor { public ExtendThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) { super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue); } @Override protected void afterExecute(Runnable r, Throwable t) { super.afterExecute(r, t); if (t != null) { System.out.println("捕获异常信息:" + t.getMessage()); } } }
运行结果如下,异常也被统一处理了:
线程接口扩展
前面的做法是在线程池的纬度去做异常处理,那有没有办法从线程的纬度呢?
JDK 也提供了相关的接口,JVM 监控到某个线程因未捕获的异常退出时,会调用该线程预先设置的异常处理器,如果没有提供任何的异常处理器,那么默认的行为就是将堆栈信息输出到控制台。
要自定义线程池中的线程,可以通过继承 ThreadFactory 工厂类,另需要实现 Thread.UncaughtExceptionHandler 接口,进行定制化的异常处理,并将 handler 对象设置到创建完成的线程上。
class customThreadFactory implements ThreadFactory { private ThreadFactory threadFactory = Executors.defaultThreadFactory(); private Thread.UncaughtExceptionHandler exceptionHandler; public customThreadFactory(Thread.UncaughtExceptionHandler exceptionHandler) { this.exceptionHandler = exceptionHandler; } public Thread newThread(Runnable r) { Thread t = threadFactory.newThread(r); t.setUncaughtExceptionHandler(exceptionHandler); return t; } } class ThreadExceptionHandler implements Thread.UncaughtExceptionHandler { @Override public void uncaughtException(Thread t, Throwable e) { System.out.println("捕获异常信息:" + e.getMessage()); } } public class MainTest { public static void main(String[] args) { ExecutorService threadPool = new ThreadPoolExecutor(5, 5, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), new customThreadFactory(new ThreadExceptionHandler())); for (int i = 0; i < 5; i++) { threadPool.execute(() -> { System.out.println("current thread: " + Thread.currentThread().getName()); int res = 1 / 0; }); } } }
运行结果如下:
总结
今天的内容主要涉及任务执行过程中的异常处理,除了提到线程池的默认处理方式,还分享了几种自定义处理方案:
- 任务内部对异常进行捕获
- FutherTask 捕获异常
- 重写线程池的 afterExecute() 方法
- 实现 Thread.UncaughtExceptionHandler 接口
希望通过本篇文章,你对子线程的异常处理有了更深刻的认识,如果觉得文章对你有帮助,欢迎留言点赞。
- 点赞
- 收藏
- 分享
- 文章举报
- Spring Boot系列十一 从源码的角度理解Spring MVC异常处理原理
- nodejs笔记之:事件驱动,线程池,非阻塞,异常处理等
- Java线程池「异常处理」正确姿势:有病就得治
- ThreadPoolExcutor 线程池 异常处理 (下篇)
- Java线程池「异常处理」正确姿势:有病就得治
- SpringMVC源码分析-400异常处理流程及解决方法
- 线程池异常统一处理
- 源码-PL/SQL从入门到精通-第十二章-异常处理机制-Part 1
- Java ExecutorService线程池中的小坑——关于线程池中抛出的异常处理
- Java源码-第一个异常处理小程序:整数除法
- 线程系列04,传递数据给线程,线程命名,线程异常处理,线程池
- Java Executor并发框架(十三)Executor框架线程池关于异常的处理
- ABP源码分析四十七:ABP中的异常处理
- spring boot——从源码的角度理解Spring MVC异常处理原理
- javaweb异常提示信息统一处理(使用springmvc,附源码)
- jstorm源码解析之bolt异常处理方法
- android源码解析(二十三)-->Android异常处理流程
- 小白学Spring源码(一):spring源码环境搭建以及异常处理
- Java并发编程(四)未处理异常、线程池和ThreadLocal类
- 网狐源码的异常处理流程优化