线程池的异常处理机制

起因

一次开发过程中,送审之后向三方OA系统推送代办,其中由于优化的原因使用到线程池

ExecutorService todoMessageAsyncThread = ThreadPoolManager.getThreadPool("todoMessageAsyncThreadPool");todoMessageAsyncThread.submit(() -> {log.info("processKey:{}, processInstanceId:{}, 开启异步线程推送待办报文。", processKey, processInstanceId);//....实现具体业务log.info("processKey:{}, processInstanceId:{}, 结束异步线程推送待办报文。", processKey, processInstanceId);});

中间有一个判断,该不该推送,然后使用el表达式进行判断,但是测试环境中el表达式配置的不标准,导致现象就是没有推送,也没有日志,子线程就像停住了,没啥动静了。

原因分析

Executors线程池有两种提交线程的方式execute和submit方式,简单测试如下:

    @Testpublic void submitTest()throws InterruptedException {Runnable runnable = () -> {int i = 1/0;};ExecutorService threadPool1 = Executors.newFixedThreadPool(5);System.out.println("execute开始执行");threadPool1.execute(runnable);Thread.sleep(1000);System.out.println("--------------------------");System.out.println("submit开始执行");Future<?> submit = threadPool1.submit(runnable);System.out.println("submit返回结果:"+submit);
/*
execute开始执行
Exception in thread "pool-1-thread-1" java.lang.ArithmeticException: / by zeroat test.java.util.concurrent.ExecutorsTest.lambda$submitTest$1(ExecutorsTest.java:40)at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)at java.base/java.lang.Thread.run(Thread.java:834)
--------------------------
submit开始执行
submit返回结果:java.util.concurrent.FutureTask@22eeefeb[Completed exceptionally: java.lang.ArithmeticException: / by zero]*/}

从测试的结果中可以看出来,execute方法中对异常信息进行的打印,而submit方法中没有对异常信息进行打印,而是将异常信息存储在了返回的future中,只有通过future.get()才能阻塞式的获取异常。

先看看execute的源码中的实现:

    public void execute(Runnable command) {if (command == null)throw new NullPointerException();/** Proceed in 3 steps:** 1 如果运行的线程少于corePoolSize,请尝试以给定的命令作为第一个任务来启动一个新线程。对addWorker的调用以原子方式检查runState和workerCount,从而通过返回false来防止错误警报,这些错误警报会在不应该添加线程的情况下添加线程。* 2. 如果一个任务可以成功排队,那么我们仍然需要仔细检查我们是否应该添加一个线程(因为自上次检查以来已有的线程已经失效),或者池是否在进入该方法后关闭。因此,我们重新检查状态,如果有必要,如果停止,则回滚排队,如果没有,则启动一个新线程。* 3.  如果我们无法对任务进行排队,那么我们将尝试添加一个新线程。如果它失败了,我们知道我们已经关闭或饱和了,所以拒绝执行任务*/int c = ctl.get();  //这里使用32位的int型数据,前3位代表状态,后29位代表线程数,在多线程环境下避免状态恶化线程数不一致if (workerCountOf(c) < corePoolSize) {if (addWorker(command, true)) //当前线程数少于核心线程数,直接添加到worker中return;c = ctl.get();}if (isRunning(c) && workQueue.offer(command)) { //如果不能插入核心线程中,就放入到queue中int recheck = ctl.get();if (! isRunning(recheck) && remove(command)) //重新检查状态reject(command);else if (workerCountOf(recheck) == 0) //queue满了addWorker(null, false);}else if (!addWorker(command, false))  //queue满了,放入最大线程中reject(command);}

其中最重要的启动子线程的方法是addWorker方法,将线程封装成Runable,传入execute方法中。
Worker也是一个线程,运行的时候调用worker的run方法:

    public void run() {runWorker(this);}final void runWorker(Worker w) {Thread wt = Thread.currentThread();Runnable task = w.firstTask;w.firstTask = null;w.unlock(); // allow interruptsboolean completedAbruptly = true;try {while (task != null || (task = getTask()) != null) {w.lock();// If pool is stopping, ensure thread is interrupted;// if not, ensure thread is not interrupted.  This// requires a recheck in second case to deal with// shutdownNow race while clearing interruptif ((runStateAtLeast(ctl.get(), STOP) ||(Thread.interrupted() &&runStateAtLeast(ctl.get(), STOP))) &&!wt.isInterrupted())wt.interrupt();try {beforeExecute(wt, task);try {task.run();     //自定义任务的run方法afterExecute(task, null);} catch (Throwable ex) {afterExecute(task, ex);throw ex;   //执行后有异常抛异常}} finally {task = null;w.completedTasks++;w.unlock();}}completedAbruptly = false;} finally {processWorkerExit(w, completedAbruptly);}}

再来看submit方法的实现源码:

    public Future<?> submit(Runnable task) {if (task == null) throw new NullPointerException();RunnableFuture<Void> ftask = newTaskFor(task, null);execute(ftask);return ftask;}

submit方法在中间调用了execute方法,但是是将子线程封装成了FutureTask,然后调用的execute方法。
这样在执行这个子线程的时候会执行FutureTask的run方法,而在run方法中,callable.call()方法直接被catch,然后将异常信息使用setException方法获取,并将异常设置到outcome里,不会抛异常出去。源码如下:

    public void run() {if (state != NEW ||!RUNNER.compareAndSet(this, null, Thread.currentThread()))return;try {Callable<V> c = callable;if (c != null && state == NEW) {V result;boolean ran;try {result = c.call(); //callable的接口ran = true;} catch (Throwable ex) {result = null;ran = false;setException(ex); //这里直接吃掉了}if (ran)set(result);}} finally {// runner must be non-null until state is settled to// prevent concurrent calls to run()runner = null;// state must be re-read after nulling runner to prevent// leaked interruptsint s = state;if (s >= INTERRUPTING)handlePossibleCancellationInterrupt(s);}}

解决方案

  1. 如果没有返回值,建议直接使用execute,不要使用submit
  2. 自己在业务代码中try-catch-finally
  3. 重写Runnable的afterExecute
//1.创建一个自己定义的线程池ExecutorService executorService = new ThreadPoolExecutor(2,3,0,TimeUnit.MILLISECONDS,new LinkedBlockingQueue(10)) {//重写afterExecute方法@Overrideprotected void afterExecute(Runnable r, Throwable t) {//这个是excute提交的时候if (t != null) {System.out.println("afterExecute里面获取到excute提交的异常信息,处理异常" + t.getMessage());}//如果r的实际类型是FutureTask 那么是submit提交的,所以可以在里面get到异常if (r instanceof FutureTask) {try {Future<?> future = (Future<?>) r;//get获取异常future.get();} catch (Exception e) {System.out.println("afterExecute里面获取到submit提交的异常信息,处理异常" + e);}}}};//当线程池抛出异常后 executeexecutorService.execute(new task());//当线程池抛出异常后 submitexecutorService.submit(new task());}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/155778.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Leetcode Sheet】Weekly Practice 16

Leetcode Test 1334 阈值距离内邻居最少的城市(11.14) 有 n 个城市&#xff0c;按从 0 到 n-1 编号。给你一个边数组 edges&#xff0c;其中 edges[i] [fromi, toi, weighti] 代表 fromi 和 toi 两个城市之间的双向加权边&#xff0c;距离阈值是一个整数 distanceThreshold。…

Hibernate 一级缓存,二级缓存,查询缓存

概念&#xff1a; 1.什么是缓存呢&#xff1f; 缓存&#xff1a;是计算机领域的概念&#xff0c;它介于应用程序和永久性数据存储源之间。 缓存&#xff1a;一般人的理解是在内存中的一块空间&#xff0c;可以将二级缓存配置到硬盘。用白话来说&#xff0c;就是一个存储数据的…

Web前端—移动Web第三天(移动Web基础、rem、less、综合案例—极速问诊)

版本说明 当前版本号[20231120]。 版本修改说明20231120初版 目录 文章目录 版本说明目录移动 Web 第三天01-移动 Web 基础谷歌模拟器屏幕分辨率视口二倍图适配方案 02-rem简介媒体查询rem 布局flexible.jsrem 移动适配 03-less注释运算嵌套变量导入导出禁止导出 04-综合案例…

GNSS技术在灾害监测与应急响应中的关键作用

全球导航卫星系统&#xff08;GNSS&#xff09;技术在灾害监测与应急响应领域发挥着重要作用&#xff0c;为预防、监测和应对自然灾害提供了关键数据支持。本文将深入探讨GNSS技术在灾害监测与应急响应中的作用&#xff0c;并分析其对提高应对灾害能力的重要性。 一、GNSS在灾害…

android报错

&#xff08;gradle版本&#xff1a;7.5-all.zip; gradle插件&#xff1a;7.4.2&#xff1b;java:11) 报错1&#xff1a; java.lang.IllegalArgumentException: Can only use lower 16 bits for requestCode 2023-11-20 19:39:39.207 22390-22390 AndroidRuntime com…

InnoDB 的一次更新事务是怎么实现的?

大体流程&#xff1a; 步骤: 1.加载数据到缓存中&#xff08;Buffer Pool&#xff09;&#xff1a; 在进行数据更新时&#xff0c;InnoDB首先会在缓冲池&#xff08;Buffer Pool&#xff09;中查找该记录是否已经在内存中。如果记录不在内存中&#xff0c;会将需要更新的数据…

2021年03月 Scratch(二级)真题解析#中国电子学会#全国青少年软件编程等级考试

Scratch等级考试(1~4级)全部真题・点这里 一、单选题(共25题,每题2分,共50分) 第1题 小猫在沙漠中旅行好不容易找到了一杯水,初始位置如下图所示,下面哪个程序可以帮助它成功喝到水? A: B: C: D:

基于像素特征的kmeas聚类的图像分割方案

kmeans聚类代码 将像素进行聚类&#xff0c;得到每个像素的聚类标签&#xff0c;默认聚类簇数为3 def seg_kmeans(img,clusters3):img_flatimg.reshape((-1,3))# print(img_flat.shape)img_flatnp.float32(img_flat)criteria(cv.TERM_CRITERIA_MAX_ITERcv.TERM_CRITERIA_EPS,2…

stack和queue简单实现(容器适配器)

容器适配器 stack介绍stack模拟实现queue 介绍queue模拟实现deque stack介绍 stack模拟实现 以前我们实现stack&#xff0c;需要像list,vector一样手动创建成员函数&#xff0c;成员变量。但是stack作为容器适配器&#xff0c;我们有更简单的方法来实现它。 可以利用模板的强大…

练习六-使用Questasim来用verilog使用function函数

[TOC](使用Questasim来用verilog使用function函数 1&#xff0c;verilog中使用函数function2&#xff0c;RTL代码3&#xff0c;测试代码4&#xff0c;输出波形 1&#xff0c;verilog中使用函数function 目的&#xff1a; &#xff08;1&#xff09;了解函数的定义和在模块设计中…

【面试】测试/测开(未完成版)

1. 黑盒测试方法 黑盒测试&#xff1a;关注的是软件功能的实现&#xff0c;关注功能实现是否满足需求&#xff0c;测试对象是基于需求规格说明书。 1&#xff09;等价类&#xff1a;有效等价类、无效等价类 2&#xff09;边界值 3&#xff09;因果图&#xff1a;不同的原因对应…

日常办公:批处理编写Word邮件合并获取图片全路径

大家在使用Word邮件合并这个功能&#xff0c;比如制作席卡、贺卡、准考证、员工档案、成绩单、邀请函、名片等等&#xff0c;那就需要对图片路径进行转换处理&#xff0c;此脚本就是直接将图片的路径提取出来&#xff0c;并把内容放到txt格式的文本文档里&#xff0c;打开Excel…

AcWing 4. 多重背包问题 I 学习笔记

有 N&#xfffd; 种物品和一个容量是 V&#xfffd; 的背包。 第 i&#xfffd; 种物品最多有 si&#xfffd;&#xfffd; 件&#xff0c;每件体积是 vi&#xfffd;&#xfffd;&#xff0c;价值是 wi&#xfffd;&#xfffd;。 求解将哪些物品装入背包&#xff0c;可使物…

JudgeOpen整理

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、pandas是什么&#xff1f;二、使用步骤 1.引入库2.读入数据总结 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; 例如&#xff1a;…

九韵和声 饕餮盛宴丨音乐和声与校友情谊的完美交融

“九韻和聲”音樂會於11月19日晚上在深圳大劇院盛大舉行。來自各高校深圳校友會的逾千名同學們歡聚一堂&#xff0c;共同慶祝自己的合唱音樂會。 首次舉辦合唱音樂會 “九韵和声”音乐会由深圳市西安交通大学校友会牵头发起、主办&#xff0c;与深圳市清华大学校友会、深圳市浙…

基于非链式(数组)结点结构的二叉树的前(先)序输入创建以及遍历

点击链接返回标题->基于非链式(数组)结点结构的二叉树的层序、先序、中序、后序输入创建以及层序、先序、中序、后序输出-CSDN博客 我们采用递归的思想&#xff0c;不断去找空结点&#xff08;值为-1的结点&#xff09;&#xff0c;在找空结点这个过程中&#xff0c;将输入的…

微信小程序知识付费平台,公众号App+SAAS+讲师端,多端部署

三勾知识付费系统基于thinkphp8element-plusuniapp打造的面向开发的知识付费系统&#xff0c;方便二次开发或直接使用&#xff0c;可发布到多端&#xff0c;包括微信小程序、微信公众号、QQ小程序、支付宝小程序、字节跳动小程序、百度小程序、android端、ios端。 功能包含直播…

欧拉操作系统下离线安装字体的操作步骤

背景 某 Web 应用部署到欧拉操作系统后&#xff0c;应用中导出的 PDF 文件中文全部显示乱码&#xff0c;原因是字体缺失&#xff0c;但是目标系统上并没有联网&#xff0c;必须找到字体的离线安装包。 CSDN 上还有40个积分&#xff0c;下载了两个相关的资源后&#xff0c;目标…

OpenAI 董事会宫斗始作俑者?一窥伊尔亚·苏茨克维内心世界

OpenAI 董事会闹剧应该是暂告一个段落了,Sam Altman和Greg Brockman等一众高管均已加入微软,还有员工写联名信逼宫董事会的戏码,关注度已经降下来了。 但是,这场宫斗闹剧的中心人物Ilya Sutskever大家关注度不算太高。他本人是纯粹的技术男,极少抛头露面透露其内心世界。…

FISCO BCOS 3.0【01】搭建第一个区块链网络

官方技术文档:https://fisco-bcos-doc.readthedocs.io/zh-cn/latest/index.html 我们在官方技术文档的基础上,进行,对文档中一些不清楚的地方进行修正 搭建Air版本FISCO BCOS联盟链本节以搭建单群组FISCO BCOS链为例操作,使用开发部署工具build_chain.sh脚本在本地搭建一条…