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中线程池的常见用法以及 ...
随机推荐
- metasploit支持利用的CVE
因为需要添加许多漏洞的流量检测,所以需要模拟很多漏洞的利用过程,简单来说,就是抓漏洞利用过程的流量. 一个脚本对metasploit中的module中包含的cve字段进行提取,而后去重,得出metas ...
- Linux学习笔记 1 环境变量 2 vi命令
1 环境变量篇 1.1 修改 查看 生效 系统环境变量 1 涉及系统环境变量的文件 --> .bash_profile --> /etc/profile 2 该文件位置 /root ...
- Numpy np.array 相关常用操作学习笔记
1.np.array构造函数 用法:np.array([1,2,3,4,5]) 1.1 numpy array 和 python list 有什么区别? 标准Python的列表(list)中,元素本质 ...
- 块级元素或者行内元素在设置float属性之后是否改变元素的性质?
块级元素使用float属性后,将其属性变成inline-block,不能改变其块级元素的性质,只是能有块级元素的特性,不独占一行,宽度不会占满父元素,和行内元素排列成一行 行内元素使用float属性后 ...
- python基础一 ------如何统计一个列表元素的频度
如何统计一个列表元素的频度 两个需求: 1,统计一个随机序列[1,2,3,4,5,6...]中的出现次数前三的元素及其次数 2,统计一片英文文章中出现次数前10 的单词 两种方法: 1,普通的for循 ...
- redis安装,第一天
1.直接官网下载 2.进入redis安装目录 : cd /redis-4.0.11 make install 3.启动:redis-server /myredis/redis.conf //复制 ...
- C++学习笔记49:栈
栈是一种只能从一端访问的线性数据结构,栈是一种后进先出的数据结构 //stack.h #ifndef STACK_H #define STACK_H #include <cassert> ...
- linux中查看 php.ini 的存放位置
查找php.ini的存放位置: 方法一: php --ini 所列出的结果中: Loaded Configuration File 即为 php.ini 所存放的位置 方法二: php -i | g ...
- JS_高程5.引用类型(2)Array类型
Array类型: ECMAScript数组的每一项可以保存任何类型的数据,数组的大小是可以动态调整的. 创建数组的基本方式: (1)使用Array构造函数 var color=new Array(); ...
- 微信小程序中的单位
vw:viewpoint width,视窗宽度,1vw等于视窗宽度的1%. vh:viewpoint height,视窗高度,1vh等于视窗高度的1%. rpx:rpx单位是微信小程序中css的尺寸单 ...