线程池吞掉异常的case:源码阅读与解决方法

1. 问题背景

有一天给同事CR,看到一段这样的代码

try {for (param : params) {//并发处理,func无返回值ThreadPool.submit(func(param));}
} catch (Exception e) {log.info("func抛异常啦,参数是:{}", param)
}

我:你这段代码是利用并发降低RT对吧,如果func内部抛异常,你确定可以catch到吗

同事:可以啊, 为什么不可以(...

我:不如你run一把,在func mock一个异常出来试试

同事:我靠还真是

我:你可以用execute,改动比较小

同事:那么是为什么呢

2. 同事的例子

import java.util.concurrent.*;public class ThreadPoolTest {public static void main(String[] args) throws Exception {ExecutorService executorService = new ThreadPoolExecutor(10, 20, 10, TimeUnit.SECONDS, new LinkedBlockingQueue<>());testExecute(executorService);Thread.sleep(2000);testSubmit1(executorService);Thread.sleep(2000);testSubmit2(executorService);}private static void testExecute(ExecutorService executorService) {executorService.execute(() -> {System.out.println("执行线程池execute方法");throw new RuntimeException("execute方法抛出异常");});}private static void testSubmit1(ExecutorService executorService) {executorService.submit(() -> {System.out.println("执行线程池submit方法1");throw new RuntimeException("submit方法1抛出异常");});}private static void testSubmit2(ExecutorService executorService) throws Exception {Future<Object> feature = executorService.submit(() -> {System.out.println("执行线程池submit方法2");throw new RuntimeException("submit方法2抛出异常");});feature.get();}
}

执行结果:

执行线程池execute方法
Exception in thread "pool-1-thread-1" java.lang.RuntimeException: execute方法抛出异常at ThreadPoolTest.lambda$testExecute$0(ThreadPoolTest.java:23)at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)at java.lang.Thread.run(Thread.java:748)
执行线程池submit方法1
执行线程池submit方法2
Exception in thread "main" java.util.concurrent.ExecutionException: java.lang.RuntimeException: submit方法2抛出异常at java.util.concurrent.FutureTask.report(FutureTask.java:122)at java.util.concurrent.FutureTask.get(FutureTask.java:192)at ThreadPoolTest.testSubmit2(ThreadPoolTest.java:39)at ThreadPoolTest.main(ThreadPoolTest.java:17)
Caused by: java.lang.RuntimeException: submit方法2抛出异常at ThreadPoolTest.lambda$testSubmit2$2(ThreadPoolTest.java:37)at java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:266)at java.util.concurrent.FutureTask.run(FutureTask.java)at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)at java.lang.Thread.run(Thread.java:748)

3. 原理分析

3.1 线程池包的继承结构

3.2 submit和execute方法的差异

3.2.1 execute

方法定义在最顶层的Executor接口,并且Executor接口有且仅有这一个方法

public interface Executor {/*** Executes the given command at some time in the future.  The command* may execute in a new thread, in a pooled thread, or in the calling* thread, at the discretion of the {@code Executor} implementation.** @param command the runnable task* @throws RejectedExecutionException if this task cannot be* accepted for execution* @throws NullPointerException if command is null*/void execute(Runnable command);
}

方法实现在ThreadPoolExecutor:

    public void execute(Runnable command) {if (command == null)throw new NullPointerException();int c = ctl.get();if (workerCountOf(c) < corePoolSize) {if (addWorker(command, true))return;c = ctl.get();}if (isRunning(c) && workQueue.offer(command)) {int recheck = ctl.get();if (! isRunning(recheck) && remove(command))reject(command);else if (workerCountOf(recheck) == 0)addWorker(null, false);}else if (!addWorker(command, false))reject(command);}

实际执行的过程,在worker(是runnable的实现类)的run方法,run方法实际执行的是runWorker方法

    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);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);}}

可以看到执行过程中,如果task.run();发生异常,没有catch处理,异常会层层向外抛出;最终进入finally块,执行processWorkerExit;

3.2.2 submit

submit方法定义在ExecutorService

public interface ExecutorService extends Executor {/*** Submits a value-returning task for execution and returns a* Future representing the pending results of the task. The* Future's {@code get} method will return the task's result upon* successful completion.** <p>* If you would like to immediately block waiting* for a task, you can use constructions of the form* {@code result = exec.submit(aCallable).get();}** <p>Note: The {@link Executors} class includes a set of methods* that can convert some other common closure-like objects,* for example, {@link java.security.PrivilegedAction} to* {@link Callable} form so they can be submitted.** @param task the task to submit* @param <T> the type of the task's result* @return a Future representing pending completion of the task* @throws RejectedExecutionException if the task cannot be*         scheduled for execution* @throws NullPointerException if the task is null*/<T> Future<T> submit(Callable<T> task);/*** Submits a Runnable task for execution and returns a Future* representing that task. The Future's {@code get} method will* return the given result upon successful completion.** @param task the task to submit* @param result the result to return* @param <T> the type of the result* @return a Future representing pending completion of the task* @throws RejectedExecutionException if the task cannot be*         scheduled for execution* @throws NullPointerException if the task is null*/<T> Future<T> submit(Runnable task, T result);/*** Submits a Runnable task for execution and returns a Future* representing that task. The Future's {@code get} method will* return {@code null} upon <em>successful</em> completion.** @param task the task to submit* @return a Future representing pending completion of the task* @throws RejectedExecutionException if the task cannot be*         scheduled for execution* @throws NullPointerException if the task is null*/Future<?> submit(Runnable task);
}

实现在AbstractExecutorService

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

可以看到这里创建了RunnableFuture(而不是基础的worker),顾名思义,RunnableFuture同时实现了Runnable和Future接口,也就意味着可以对该任务执行get操作,看看RunnableFuture的run方法:

    public void run() {if (state != NEW ||!UNSAFE.compareAndSwapObject(this, runnerOffset,null, Thread.currentThread()))return;try {Callable<V> c = callable;if (c != null && state == NEW) {V result;boolean ran;try {result = c.call();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);}}

catch块对方法异常做了处理,与执行结果一同在Future中暂存起来;submit()执行完毕后返回Future对象,执行future.get()会触发异常的抛出;

当然了,如果你只是执行了submit,没有获取future,异常就会“神奇地”消失。

参考:

Java线程池实现原理及其在美团业务中的实践 - 美团技术团队

https://zhuanlan.zhihu.com/p/651997713

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

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

相关文章

【数据结构与算法 刷题系列】求带环链表的入环节点(图文详解)

&#x1f493; 博客主页&#xff1a;倔强的石头的CSDN主页 &#x1f4dd;Gitee主页&#xff1a;倔强的石头的gitee主页 ⏩ 文章专栏&#xff1a;《数据结构与算法 经典例题》C语言 期待您的关注 ​ 目录 一、问题描述 二、解题思路 方法一&#xff1a;数学公式推导法 方法…

苏州辰安塑业携塑料托盘、塑料物流箱解决方案亮相2024杭州快递物流展

苏州辰安塑业携塑料托盘、吹塑托盘、塑料卡板箱、塑料周转箱、塑料物流箱、塑料垃圾桶解决方案盛装亮相2024杭州快递物流展&#xff01; 展位号&#xff1a;3C馆A51 苏州辰安塑业有限公司&#xff0c;是一家专业从事塑料托盘、吹塑托盘、塑料卡板箱、塑料周转箱、塑料物流箱、…

【前端】Nesj 学习笔记

1、前置知识 1.1 装饰器 装饰器的类型 declare type ClassDecorator <TFunction extends Function>(target: TFunction) > TFunction | void; declare type PropertyDecorator (target: Object, propertyKey: string | symbol) > void; declare type MethodDe…

大模型应用开发技术:Multi-Agent框架流程、源码及案例实战(二)

LlaMA 3 系列博客 基于 LlaMA 3 LangGraph 在windows本地部署大模型 &#xff08;一&#xff09; 基于 LlaMA 3 LangGraph 在windows本地部署大模型 &#xff08;二&#xff09; 基于 LlaMA 3 LangGraph 在windows本地部署大模型 &#xff08;三&#xff09; 基于 LlaMA…

05-5.4.1 树的存储结构

&#x1f44b; Hi, I’m Beast Cheng &#x1f440; I’m interested in photography, hiking, landscape… &#x1f331; I’m currently learning python, javascript, kotlin… &#x1f4eb; How to reach me --> 458290771qq.com 喜欢《数据结构》部分笔记的小伙伴可以…

经验分享,xps格式转成pdf格式

XPS 是一种电子文档格式、后台打印文件格式和页面描述语言。有时候微软默认打印机保存的是xps格式&#xff0c;我们如何转换为pdf格式呢&#xff0c;这里分享一个免费好用的网站&#xff0c;可以实现。 网站&#xff1a;https://xpstopdf.com/zh/ 截图&#xff1a;

JVM 三色标记算法

三色标记算法核心原理 三色标记算法是一种JVM的垃圾标记算法&#xff0c;CMS/G1垃圾回收器就是使用的这种算法&#xff0c;它可以让JVM在不发生或者尽可能短的发生STW&#xff08;Stop The World&#xff09;的情况下进行垃圾的标记和清除。 顾名思义&#xff0c;三色标记算法…

Linux(Centos7)OpenSSH漏洞修复,升级最新openssh-9.7p1

OpenSSH更新 一、OpenSSH漏洞二、安装zlib三、安装OpenSSL四、安装OpenSSH 一、OpenSSH漏洞 服务器被扫描出了漏洞需要修复&#xff0c;准备升级为最新openssh服务 1. 使用ssh -v查看本机ssh服务版本号 ssh -V虚拟机为OpenSSH7.4p1&#xff0c;现在准备升级为OpenSSH9.7p1…

springboot小型超市商品展销系统-计算机毕业设计源码01635

摘 要 科技进步的飞速发展引起人们日常生活的巨大变化&#xff0c;电子信息技术的飞速发展使得电子信息技术的各个领域的应用水平得到普及和应用。信息时代的到来已成为不可阻挡的时尚潮流&#xff0c;人类发展的历史正进入一个新时代。在现实运用中&#xff0c;应用软件的工作…

“暗蚊”黑产团伙通过国内下载站传播Mac远控木马攻击活动分析

黑客&网络安全如何 1 概述 近期&#xff0c;安天CERT发现一组利用非官方软件下载站进行投毒和攻击下游用户案例&#xff0c;并深入分析了攻击者在网管运维工具上捆绑植入macOS平台远控木马&#xff0c;利用国内非官方下载站发布&#xff0c;以此取得政企机构内部…

nvm 报错https://npm.taobao.org/mirrors/node/index.json 淘宝镜像更换

文章目录 一、问题背景二、解决问题1. 获取配置文件的位置2. 修改配置文件中的镜像源配置3. 修改 npm 镜像源 一、问题背景 使用nvm的时候报错: Could not retrieve https://npm.taobao.org/mirrors/node/index.json. 由于淘宝的镜像域名更换&#xff0c;npm.taobao.org 域名…

【PowerDesigner】创建和管理CDM之新建实体

目录 &#x1f30a;1. PowerDesigner简介 &#x1f30d;1.1 常用模型文件 &#x1f30d;1.2 PowerDesigner使用环境 &#x1f30a;2. 创建和管理CDM &#x1f30d;​​​​​​2.1 新建CDM &#x1f30d;2.2 新建实体 &#x1f30a;3. 研究心得 &#x1f30a;1. PowerDe…

qss实现登录界面美化

qss实现登录界面美化 #include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);// 去掉头部this->setWindowFlag(Qt::FramelessWindowHint);// 去掉空白部分th…

元数据:数据的罗塞塔石碑

在大数据时代&#xff0c;我们每天都在生成和处理海量数据。但数据本身&#xff0c;如果没有适当的上下文和描述&#xff0c;就像是一堆没有翻译的古老文字。这就是元数据发挥作用的地方——它是大数据世界的罗塞塔石碑&#xff0c;为我们提供了理解和利用数据的关键。 文章目录…

期末考试老师怎样发成绩

期末成绩的公布&#xff0c;总是让老师感到焦虑。成绩&#xff0c;这一张张的数字&#xff0c;承载着学生一学期的努力&#xff0c;也牵动着家长们的心。 传统的成绩公布方式&#xff0c;写成绩条让学生带回家&#xff0c;或是通过私发家长的方式&#xff0c;都存在一定的弊端。…

记录清除挖矿病毒 solrd 过程

1、发现solrd病毒 端午节期间&#xff0c;kafka 服务器被黑客攻击了&#xff0c;植入了挖矿病毒 solrd&#xff0c;这个病毒很聪明&#xff0c;内存&#xff0c;CPU并没有异常升高&#xff0c;以致于上班第一天完全没有察觉。 上班第一天 正常登录服务器查看 flink ,消费kafka…

云和运维(SRE)的半生缘-深读实证02

这个标题不算太夸张&#xff0c;云计算和很多IT岗位都有缘&#xff0c;但是和运维&#xff08;SRE&#xff09;岗位的缘分最深。 “深读实证”系列文章都会结合一些外部事件&#xff0c;点明分析《云计算行业进阶指南》书中的内容。本次分享介绍了下列内容&#xff1a; 我以运维…

ctfshow web 单身杯

web签到 <?phperror_reporting(0); highlight_file(__FILE__);$file $_POST[file];if(isset($file)){if(strrev($file)$file){ //翻转函数include $file;}}要进行反转并且包含文件用data协议 自己写不好写可以用函数帮你翻转 <?php $adata:text/plain,<?eval(…

Cisco Packet Tracer实验(五)不同vlan间的通信简单配置

1&#xff0e;单臂路由(图) 环境&#xff1a;一台路由器&#xff0c;一台二层交换机&#xff0c;两台pc机 单臂路由&#xff08;Single Arm Routing&#xff09;是指在网络架构中&#xff0c;只有一个物理接口&#xff08;单臂&#xff09;连接到路由器三层交换机&#xff0c;而…

大数据分析-二手车用户数据可视化分析

项目背景 在当今的大数据时代&#xff0c;数据可视化扮演着至关重要的角色。随着信息的爆炸式增长&#xff0c;我们面临着前所未有的数据挑战。这些数据可能来自社交媒体、商业交易、科学研究、医疗记录等各个领域&#xff0c;它们庞大而复杂&#xff0c;难以通过传统的数据处…