Java线程池UncaughtExceptionHandler无效?可能是使用方式不对

背景

在业务处理中,使用了线程池来提交任务执行,但是今天修改了一小段代码,发现任务未正确执行。而且看了相关日志,也并未打印结果。

源码简化版如下:
首先,自定义了一个线程池

public class NamedThreadFactory implements ThreadFactory {private final AtomicInteger threadNumber = new AtomicInteger(1);private final String namePrefix;private final ThreadGroup group;private final Logger logger = LoggerFactory.getLogger(this.getClass());public NamedThreadFactory(String namePrefix) {SecurityManager s = System.getSecurityManager();group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();this.namePrefix = namePrefix + "-thread-";}@Overridepublic Thread newThread(Runnable r) {Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0);t.setUncaughtExceptionHandler(new ThreadUncaugthExceptionHandler());return t;}private class ThreadUncaugthExceptionHandler implements UncaughtExceptionHandler {@Overridepublic void uncaughtException(Thread t, Throwable e) {logger.error("uncaughtException thead name:{}, msg:{}", t.getName(), e.getMessage(), e);}}
}

线程池A如下所示

ThreadPoolExecutor EXECUTOR_A = 
new ThreadPoolExecutor(1, 1, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(100),new NamedThreadFactory("AService-"));

待执行任务

EXECUTOR_A.submit(() -> {// 处理step1......// 以下是本次新增代码ZoneId zoneId = ZoneId.of("Asia/Shanghai");LocalDate now = LocalDate.now(zoneId);LocalDate endTime = now.plus(1, ChronoUnit.YEARS);//final变量DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");String endTIme = DATE_TIME_FORMATTER.format(endTime);// 调用B的处理方法});

对于上述新增的代码,会报以下异常

java.time.temporal.UnsupportedTemporalTypeException: Unsupported field: HourOfDay

这是因为希望格式化的是yyyy-MM-dd HH:mm:ss格式,我使用的是LocalDate,实际应该使用LocalDateTime才对。

解析

从背景中可以看到新增的代码由于书写错误,会报异常。

同时由于exeuctor提交的Runnable任务中缺少try-catch相应处理,那么该任务会执行失败。但是这里有一个奇怪的地方,明明给线程池自定了ThreadFactory,并且指定了UncaughtExceptionHandler,里面应该会打印错误日志才对。

可是翻遍了日志,却一点没有找到。
到这里有一些朋友可能已经知道问题了,问题的关键就在于任务提交的方式,也就是submit和execute的差异。

概况一下,在Executor框架中,线程池提供了两个方法用于提交任务:execute()和submit()。这两个方法的主要区别如下:

  1. execute()方法:
    • 用于提交不需要返回值的任务,即Runnable类型的任务。
    • execute()方法将任务提交给线程池后,将立即返回,而不等待任务执行完成或返回结果。
    • 如果任务内部发生异常,线程池会捕获并抛出异常。
  2. submit()方法:
    • 用于提交需要返回值的任务,即Callable类型的任务,也可以执行Runnable,会以Void作为返回类型。
    • submit()方法将任务提交给线程池后,返回一个Future对象,可以使用该对象的get()方法获取任务执行的结果。
    • 如果任务内部发生异常,线程池会将异常封装在ExecutionException中,通过Future对象的get()方法处理抛出的ExecutionException。

对于execute方法中的异常处理,可以查看以下代码,红框中是对于RuntimeException直接抛出。

java.util.concurrent.ThreadPoolExecutor#runWorker
在这里插入图片描述

而对于submit方法来说,任务提交的时候,会创建一个FutureTask。

在这里插入图片描述在这里插入图片描述

FutureTask的run方法处理如下

在这里插入图片描述

在异常情况下,将异常赋值给了outcome。

在这里插入图片描述

而当我们调用了Future.get()方法时,

在这里插入图片描述

在这里插入图片描述

综上分析,如果是execute方式提交任务,异常会直接抛出,最终进入到自定义的UncaughtExceptionHandler。如果是submit方式提交任务,异常只会在Future.get()方法时抛出,如果并没有调用get方法,那么是不会感知到异常的。此时也就是本文中的情况,就无法看到自定义的UncaughtExceptionHandler打印的日志了。

总结

推荐的处理方式

  • 推荐try-catch对线程任务进行异常捕获
  • 推荐自定义ThreadFacory,并自定义UncaughtExceptionHandler进行异常打印,避免有一些异常捕获遗漏的情况。当然此场景下,一定要区分submit和execute任务提交方式

扩展

除了使用上面的ThreadFactory方式外,还有其他几个方式。

包装运行任务

public static class CatchingExceptionRunnable implements Runnable {private final Runnable delegate;public CatchingExceptionRunnable(Runnable delegate) {this.delegate = delegate;}@Overridepublic void run() {try {delegate.run();} catch (RuntimeException e) {// 异常处理逻辑}}
}

适用场景

  1. 同一个线程池可能在处理不同的任务,有的适用于默认ThreadPool统一的UncaughtExceptionHandler,而有的任务需要特殊处理。
  2. 在个别场景下,我们无法给使用的线程池通过指定ThreadFactory的UncaughtExceptionHandler进行异常处理,只能从任务本身处理。

覆盖ThreadPoolExecutor的afterExecute方法

java.util.concurrent.ThreadPoolExecutor#afterExecute

Method invoked upon completion of execution of the given Runnable(该方法会在任务执行完成后被调用)

该方法是在ThreadPoolExecutor的runWorker的finaly方法中触发的。
示例代码

public static class MonitoringThreadPoolExecutor extends ThreadPoolExecutor {public MonitoringThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,BlockingQueue<Runnable> workQueue) {super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);}@Overrideprotected void afterExecute(Runnable r, Throwable t) {super.afterExecute(r, t);if(t != null){System.out.println("Exception message: " + t.getMessage());}}
}

但是execute和submit方式还是有区别。
对于execute方法,此时异常就是任务抛出的异常。但是对于submit方式,此时异常时null。具体可见下图。

在这里插入图片描述

在这里插入图片描述

如果是上述代码的实现,此时通过submit提交的任务发生异常时,仍然是无法解析到的。如果要解析到,可以参照JDK给的解释和示例

public static class MonitoringThreadPoolExecutor extends ThreadPoolExecutor {public MonitoringThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,BlockingQueue<Runnable> workQueue) {super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);}@Overrideprotected void afterExecute(Runnable r, Throwable t) {super.afterExecute(r, t);if (t == null && r instanceof Future<?>) {try {Object result = ((Future<?>)r).get();} catch (CancellationException ce) {t = ce;} catch ( ExecutionException ee) {t = ee.getCause();} catch (InterruptedException ie) {Thread.currentThread().interrupt();}}if(t != null){System.out.println("Exception message: " + t.getMessage());}}
}

本质还是前面提到的在方法中使用Future.get将异常信息得到再做处理。

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

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

相关文章

Linux环境下SVN服务器的搭建与公网访问:使用cpolar端口映射的实现方法

文章目录 前言1. Ubuntu安装SVN服务2. 修改配置文件2.1 修改svnserve.conf文件2.2 修改passwd文件2.3 修改authz文件 3. 启动svn服务4. 内网穿透4.1 安装cpolar内网穿透4.2 创建隧道映射本地端口 5. 测试公网访问6. 配置固定公网TCP端口地址6.1 保留一个固定的公网TCP端口地址6…

vue3:使用:图片生成二维码并复制

实现在 vue3 中根据 url 生成一个二维码码&#xff0c;且可以复制。 注&#xff09;复制功能 navigator.clipboard.write 只能在安全的localhost 这种安全网络下使用。https中需要添加安全证书&#xff0c;且在域名&#xff08;例&#xff1a;https://www.baidu.com&#xff0…

家政服务小程序制作教程:从设计到开发的详细步骤

在当今的数字化时代&#xff0c;小程序已经成为了一种趋势&#xff0c;不仅提供了方便快捷的应用体验&#xff0c;也成为了各种行业进行营销和客户管理的有力工具。特别是对于家政行业&#xff0c;通过小程序的应用&#xff0c;可以更好地进行业务管理&#xff0c;提升服务质量…

k8s的学习篇1

一 k8s的概念 1.1 k8s k8s是一个轻量级的&#xff0c;用于管理容器化应用和服务的平台。通过k8s能够进行应用的自动化部署和扩容缩容。 1.2 k8s核心部分 1.prod: 最小的部署单元&#xff1b;一组容器的集合&#xff1b;共享网络&#xff1b;生命周期是短暂的&#xff1b; …

nginx配置keepalive长连接

nginx之keepalive详解与其配置_keepalive_timeout_恒者走天下的博客-CSDN博客 为什么要有keepalive? 因为每次建立tcp都要建立三次握手&#xff0c;消耗时间较长&#xff0c;所以为了减少tcp建立连接需要的时间&#xff0c;就可以设置keep_alive长连接。 nginx中keep_alive对…

Docker网络-探索容器网络如何相互通信

当今世界&#xff0c;企业热衷于容器化&#xff0c;这需要强大的网络技能来正确配置容器架构&#xff0c;因此引入了 Docker Networking 的概念。Docker 是一种容器化平台&#xff0c;允许您在独立、轻量级的容器中运行应用程序和服务。Docker 提供了一套强大的网络功能&#x…

时序预测 | MATLAB实现基于TSO-XGBoost金枪鱼算法优化XGBoost的时间序列预测(多指标评价)

时序预测 | MATLAB实现基于TSO-XGBoost金枪鱼算法优化XGBoost的时间序列预测(多指标评价) 目录 时序预测 | MATLAB实现基于TSO-XGBoost金枪鱼算法优化XGBoost的时间序列预测(多指标评价)预测效果基本介绍程序设计参考资料 预测效果 基本介绍 Matlab实现基于TSO-XGBoost金枪鱼算…

大数据项目实战(安装Hive)

一&#xff0c;搭建大数据集群环境 1.3 安装Hive 1.3.1 Hive的安装 1.安装MySQL服务 1&#xff09;检查是否安装MySQL&#xff0c;如安装将其卸载。卸载命令 rpm -qa | grep mysql 2&#xff09;搜索MySQL文件夹&#xff0c;如存在则删除 find / -name mysql rm -rf /etc/s…

Ceph入门到精通-如何编译安装Quagga?

Quagga 1. 理论部分 1.1 软件简介 Quagga中文翻译斑驴&#xff0c;是一种先进的路由软件包&#xff0c;提供一套基于TCP/IP的路由协议。 1.2 斑驴的应用场景 – 使得操作系统变成专业的路由 – 使得操作系统具有与传统路由通过路由协议直接对接 1.3 斑驴支持的路由协议 …

2分钟搭建自己的GPT网站

如果觉得官方免费的gpt&#xff08;3.5&#xff09;体验比较差&#xff0c;总是断开&#xff0c;或者不会fanqiang&#xff0c;那你可以自己搭建一个。但前提是你得有gpt apikey。年初注册的还有18美金的额度&#xff0c;4.1号后注册的就没有额度了。不过也可以自己充值。 有了…

五度易链最新“产业大数据服务解决方案”亮相,打造数据引擎,构建智慧产业

快来五度易链官网 点击网址【http://www.wdsk.net/】 看看我们都发布了哪些新功能!!! 自2015年布局产业大数据服务行业以来&#xff0c;“五度易链”作为全国产业大数据服务行业先锋企业&#xff0c;以“让数据引领决策&#xff0c;以智慧驾驭未来”为愿景&#xff0c;肩负“打…

Nginx全局配置

目录 一、修改启动进程数 二、日制分割 三、nginx进程的优先级&#xff08;work进程的优先级&#xff09; 四、http设置 4.1http 协议配置说明 4.2mime 4.3 server块构建虚拟主机 4.4 location 一、修改启动进程数 worker_processes 1; #允许的启动工作进程数数量…

webrtc的Sdp中的Plan-b和UnifiedPlan

在一些类似于视频会议场景下&#xff0c;媒体会话参与者需要接收或者发送多个流&#xff0c;例如一个源端&#xff0c;同时发送多个左右音轨的音频&#xff0c;或者多个摄像头的视频流&#xff1b;在2013年&#xff0c;提出了2个不同的SDP IETF草案Plan B和Unified Plan&#x…

【C++初阶】list的常见使用操作

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前学习C和算法 ✈️专栏&#xff1a;C航路 &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章对你有帮助的话 欢迎 评论&#x1f4ac; 点赞&#x1…

Jumpserver堡垒机管理(安装和相关操作)-------从小白到大神之路之学习运维第89天

第四阶段 时 间&#xff1a;2023年8月28日 参加人&#xff1a;全班人员 内 容&#xff1a; Jumpserver堡垒机管理 目录 一、堡垒机简介 &#xff08;一&#xff09;运维常见背黑锅场景 &#xff08;二&#xff09;背黑锅的主要原因 &#xff08;三&#xff09;解决背黑…

Unity shader 入门之渲染管线一、总览

如下示意图 应用阶段(ApplicationStage)&#xff1a;准备场景信息&#xff08;视景体&#xff0c;摄像机参数&#xff09;、粗粒度剔除、定义每个模型的渲染命令&#xff08;材质&#xff0c;shader&#xff09;——由开发者定义&#xff0c;不做讨论。几何阶段(GemetryStage)&…

centos服务器系统下安装python3并与自带的python2

centos服务器系统下安装python3并与自带的python2 在centos中&#xff0c;自带有python2&#xff0c;因此需要经常安装python3。但是这里有一个坑&#xff0c;就是centos的yum是用python2写的&#xff0c;如果正常编译安装python3&#xff0c;那么yum就会直接挂了。为了方便以…

PDF校对:让您的文件无瑕疵

无论您是企业家、学生、教育者还是作家&#xff0c;我们都知道&#xff0c;提交或发布一个充满错误的PDF文件可能会给您的声誉或品牌带来严重损害。这就是为什么PDF校对如此关键的原因。现在&#xff0c;让我们深入了解PDF校对的重要性&#xff0c;以及如何确保您的文件尽可能完…

[NLP]LLM--transformer模型的参数量

1. 前言 最近&#xff0c;OpenAI推出的ChatGPT展现出了卓越的性能&#xff0c;引发了大规模语言模型(Large Language Model, LLM)的研究热潮。大规模语言模型的“大”体现在两个方面&#xff1a;模型参数规模大&#xff0c;训练数据规模大。以GPT3为例&#xff0c;GPT3的参数量…

MDK 5.xx.0 + STM32F10x 笔记

天才脑袋比不上烂笔头, 写给自己看, 自用资料。 安装MDK STM32环境 Download MDK安装 MDK -> c:\keil_v5 用默认路径下载 ARMCC V5.06 Update 7 (build960) <- 长期稳定支持版本安装至 c:\keil_v5\arm\ARMCC开启 uVision.设定 预设编译程序版本 : V5.06 Update 7 (bui…