ThreadPoolExcutor 线程池 异常处理 (下篇)
前言
因为这是之前面试的一个题目,所以印象比较深刻,前几天写了一篇文章:ThreadPoolExcutor 线程池 异常处理 (上篇) 中已经介绍了线程池异常的一些问题以及一步步分析了里面的一些源代码,今天就来继续说下如何防范这种情况。
结论
这里直接抛出结论,然后再一个个分析:
- 在我们提供的Runnable的run方法中捕获任务代码可能抛出的所有异常,包括未检测异常
- 使用ExecutorService.submit执行任务,利用返回的Future对象的get方法接收抛出的异常,然后进行处理
- 重写ThreadPoolExecutor.afterExecute方法,处理传递到afterExecute方法中的异常
- 为工作者线程设置UncaughtExceptionHandler,在uncaughtException方法中处理异常 (不推荐)
分析解读
Runnable的run方法中捕获任务代码可能抛出的所有异常
这个其实最简单,但是往往面试官问这个问题 考察的点也不在这里。具体的方式可以参考我之前的一篇文章:论如何优雅的自定义ThreadPoolExecutor线程池
核心代码如下:

使用ExecutorService.submit执行任务,利用返回的Future对象的get方法接收抛出的异常
1, 使用submit执行异步任务,然后通过Future的get方法来接收异常。演示如下:
冲图片可以看到,使用了get方法后,这里直接接收到了异常信息。

2, 这里newTaskFor返回的是FutureTask,然后传递给了execute方法:

3, 接着我们继续往下跟踪execute方法,发现这里调用的是ThreadExecutor中的execute方法,在ThreadPoolExcutor 线程池 异常处理 (上篇) 我们已经分析过这里,最终会到addWorker方法中执行线程的start()方法,因为我们在上一张图片传递的是FutureTask, 所以我们继续跟踪FutureTask中的run方法:

4, 到了FutureTask.run() 方法中,一切似乎都已经明了,这里会有catch捕获当前线程抛出的异常,紧接着我们看看setException做了什么事情:

5,setExcetion首先是将一个异常信息赋值给一个全局变量outcome,并且将全局的任务状态state字段通过CAS更新为3(异常状态)
然后最后做一些清理工作。

6,finishCompletion后续是做一些线程池的清理工作,这里涉及到线程池以及线程池中的等待队列的操作,不清楚的同学可以看下线程池实现代码。到了这里线程池中的线程执行已经完毕了,下面再去跟踪一下FutureTask.get()方法。

7,这里是FutureTask.get()的底层实现,这里其实会拿上面的setException方法中设置的outcome和state做一些逻辑判断,到了这里就直接往上抛出了异常,所以我们在最开始的main方法中才能够捕获到这个异常。

重写ThreadPoolExecutor.afterExecute方法,处理传递到afterExecute方法中的异常
这里为何要重写afterExecute方法呢?因为线程执行完毕后一定会执行此方法,源码如下:

所以我们可以重写此方法来达到接收异常的目的。
为工作者线程设置UncaughtExceptionHandler,在uncaughtException方法中处理异常 (不推荐)
1,我们在之前ThreadExecutor->Worker->run方法中直接往上抛出了异常,但是这些异常抛到哪里了呢?

2,通过查询JVM的一些资料,最终的异常会到Thread.dispatchUncaughtException中,如下图:

3,所以当我们自定义UncaughtExceptionHandler时就可以捕获到

具体测试代码如下:
/**
* 测试singleThreadPool异常问题
*
* @author: wangmeng
* @date: 2019/3/25 23:40
*/
public class ThreadPoolException {
private final static Logger LOGGER = LoggerFactory.getLogger(ThreadPoolException.class);
public static void main(String[] args) throws InterruptedException {
ExecutorService execute = new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), new ThreadFactoryBuilder().setUncaughtExceptionHandler(new MyHandler()).build());
execute.execute(new Runnable() {
@Override
public void run() {
LOGGER.info("=====11=======");
}
});
TimeUnit.SECONDS.sleep(5);
execute.execute(new Run1());
}
private static class Run1 implements Runnable {
@Override
public void run() {
int count = 0;
while (true) {
count++;
LOGGER.info("-------222-------------{}", count);
if (count == 10) {
System.out.println(1 / 0);
try {
} catch (Exception e) {
LOGGER.error("Exception",e);
}
}
if (count == 20) {
LOGGER.info("count={}", count);
break;
}
}
}
}
}
class MyHandler implements Thread.UncaughtExceptionHandler {
private final static Logger LOGGER = LoggerFactory.getLogger(MyHandler.class);
@Override
public void uncaughtException(Thread t, Throwable e) {
LOGGER.error("threadId = {}, threadName = {}, ex = {}", t.getId(), t.getName(), e.getMessage());
}
}
上面说了其实是不推荐重写UncaughtExceptionHandler 的,因为UncaughtExceptionHandler 只有在execute.execute()方法中才生效,在execute.submit中是无法捕获到异常的。
由于本人水平有限,文章中如果有不严谨的地方还请提出来,愿闻其详。
ThreadPoolExcutor 线程池 异常处理 (下篇)的更多相关文章
- ThreadPoolExcutor 线程池 异常处理 (上篇)
前言 最近看到crossoverJie的一篇文章:一个线程罢工的诡异事件 首先感谢原作者的分享,自己获益匪浅.然后是回想到自己的一次面试经历,面试官提问了线程池中的线程出现了异常该怎样捕获?会导致什么 ...
- JDK线程池异常处理方式
1. 抛出异常 execute() java.util.concurrent.ThreadPoolExecutor#runWorker 中抛出,抛出之后经过以下两个步骤: catch块捕获,捕获之后再 ...
- JAVA 线程池之Callable返回结果
本文介绍如何向线程池提交任务,并获得任务的执行结果.然后模拟 线程池中的线程在执行任务的过程中抛出异常时,该如何处理. 一,执行具体任务的线程类 要想 获得 线程的执行结果,需实现Callable接口 ...
- 面试必备:Java线程池解析
前言 掌握线程池是后端程序员的基本要求,相信大家求职面试过程中,几乎都会被问到有关于线程池的问题.我在网上搜集了几道经典的线程池面试题,并以此为切入点,谈谈我对线程池的理解.如果有哪里理解不正确,非常 ...
- Java线程池面试
New Thread的弊端 每次new Thread会新建对象,性能差 线程缺乏统一管理,可能无限制的新建线程,相互竞争,有可能占用过多系统资源导致死机或OOM 缺少更多功能,如更多执行.定期执行.线 ...
- JAVA线程池ScheduledExecutorService周期性地执行任务 与单个Thread周期性执行任务的异常处理
本文记录: 1,使用ScheduledExecutorService的 scheduleAtFixedRate 方法执行周期性任务的过程,讨论了在任务周期执行过程中出现了异常,会导致周期任务失败. 2 ...
- java 线程池第一篇 之 ThreadPoolExcutor
一:什么是线程池? java 线程池是将大量的线程集中管理的类,包括对线程的创建,资源的管理,线程生命周期的管理.当系统中存在大量的异步任务的时候就考虑使用java线程池管理所有的线程.减少系统资源的 ...
- 线程池:ThreadPoolExcutor源码阅读
ThreadPoolExcutor源码流程图:(图片较大,下载再看比较方便) 线程池里的二进制奥秘 前言: 线程池的五种状态state(RUNNING.SHUTDOWN.STOP.TIDYING.TE ...
- Java中线程池,你真的会用吗?ExecutorService ThreadPoolExcutor
原文:https://www.hollischuang.com/archives/2888 在<深入源码分析Java线程池的实现原理>这篇文章中,我们介绍过了Java中线程池的常见用法以及 ...
随机推荐
- Python常用模块--collections
collections是Python中一个非常强大的容器数据模块. 1.创建升级版的元组--namedtupe Python的元组(1,2,3)具有不可变性,但是单独的元组在无法满足现有需求时,可以使 ...
- Spring AOP 源码分析 - 拦截器链的执行过程
1.简介 本篇文章是 AOP 源码分析系列文章的最后一篇文章,在前面的两篇文章中,我分别介绍了 Spring AOP 是如何为目标 bean 筛选合适的通知器,以及如何创建代理对象的过程.现在我们的得 ...
- P3812 【模板】线性基
P3812 [模板]线性基 理解 :线性基 类似于 向量的极大无关组,就是保持原来所有数的异或值的最小集合, 求解过程也类似,可以 O( 60 * n )的复杂度求出线性基,线性基有许多性质,例如 线 ...
- vue中的jsx
一.配置文件package.json { "name": "vuetest", "version": "1.0.0", ...
- UVA 12108 Extraordinarily Tired Students
思路: ①用结构体stu,属性有清醒时间,睡眠时间,开始处于的时间,状态(醒着还是睡着), 还有计数器. ②二维数组存表格. ③在确定接下来要进入的状态之后,就一次把表格里持续状态的数据都修改掉,比如 ...
- Vue初始
一 .安装 https://cn.vuejs.org/ 官方网站 二 .简单实用示例 Vue.js 使用了基于 HTML 的模板语法,最简单的使用vue的方式是渲染数据,渲染数据最常见的形式就是使 ...
- Linux搭建 SVN 服务器
安装 Subversion Subversion 是一个版本控制系统,相对于的 RCS . CVS ,采用了分支管理系统,它的设计目标就是取代 CVS . yum install -y subvers ...
- BZOJ4095 : [Usaco2013 Dec]The Bessie Shuffle
首先将排列和整个序列以及询问都反过来,问题变成给定一个位置$x$,问它经过若干轮置换后会到达哪个位置. 每次置换之后窗口都会往右滑动一个,因此其实真实置换是$p[i]-1$. 对于每个询问,求出轮数, ...
- 2000万行表从SqlServer转移到Mongodb
就是记录一下操作过程,备忘,没什么难的
- win7 64位安装Dlib19.6版本的过程记录
本文为原创,未经允许不得转载. 1.去Dlib的官网下载dlib-19.6的源文件.然后解压到Myprograms下的Res文件夹下 2.到CMake的官网下载Cmake,我下载以后解压,然后进入到b ...