记一次ThreadPoolTaskExecutor的坑

起因:

开发环境一切正常
部署到UAT环境后,项目中使用@Async修饰的方法没有执行。

临时解决方法:

先去掉该注解改成同步执行。

问题排查过程:

1.创建一个测试controller,用于观察线程池情况

package org.example.controller;import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.example.service.MyTestService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.Map;@RequestMapping(value = "/test")
@RestController
public class MyTestController {@Autowiredprivate ApplicationContext applicationContext;@Autowiredprivate MyTestService myTestService;@GetMapping({"/threadPools"})public JSONObject threadPools(){Map<String, ThreadPoolTaskExecutor> threadMap = applicationContext.getBeansOfType(ThreadPoolTaskExecutor.class);String json = JSON.toJSONString(threadMap);JSONObject jsonObject = JSON.parseObject(json);return jsonObject;}@GetMapping(value = "/test1")public String test1(){myTestService.test1();return "ok";}}
package org.example.service;import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;@Slf4j
@Service
public class MyTestService {@Async//("taskExecutor")public void test1(){log.info("test1");}}

浏览器方法该接口:http://localhost:8080/test/threadPools

{"taskExecutor": {"activeCount": 3,"threadNamePrefix": "taskExecutor-","poolSize": 3,"threadPoolExecutor": {"activeCount": 3,"threadFactory": {"$ref": "$.taskExecutor"},"largestPoolSize": 3,"poolSize": 3,"taskCount": 3,"rejectedExecutionHandler": {},"corePoolSize": 3,"completedTaskCount": 0,"terminating": false,"maximumPoolSize": 5,"queue": [],"shutdown": false,"terminated": false},"corePoolSize": 3,"threadPriority": 5,"maxPoolSize": 5,"keepAliveSeconds": 60,"daemon": false}
}

再调用test1接口把普通任务提交到该线程池:http://localhost:8080/test/test1
再观察线程池情况:

{"taskExecutor": {"activeCount": 3,"threadNamePrefix": "taskExecutor-","poolSize": 3,"threadPoolExecutor": {"activeCount": 3,"threadFactory": {"$ref": "$.taskExecutor"},"largestPoolSize": 3,"poolSize": 3,"taskCount": 5,"rejectedExecutionHandler": {},"corePoolSize": 3,"completedTaskCount": 0,"terminating": false,"maximumPoolSize": 5,"queue": [{"cancelled": false,"done": false},{"cancelled": false,"done": false}],"shutdown": false,"terminated": false},"corePoolSize": 3,"threadPriority": 5,"maxPoolSize": 5,"keepAliveSeconds": 60,"daemon": false}
}

发现等待队列queue节点多了几个,且activeCount一直保持3,而corePoolSize刚好也是3。
此时有理由怀疑,有3个任务没有结束,导致新的任务只能放在等待队列,因此没有执行新任务。

通过本地debug发现,系统启动后,公司的框架代码会往默认线程池里提交3个任务,而这3个任务都是while(true)循环。

package org.example.event;import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.context.ApplicationEvent;
@Getter
@Setter
@ToString
public class MyEvent  extends ApplicationEvent {public MyEvent(Object source) {super(source);}
}
package org.example.init;import org.example.event.MyEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;import javax.annotation.PostConstruct;@Component
public class Initor {@Autowiredprivate ApplicationContext applicationContext;@PostConstructprivate void init(){applicationContext.publishEvent(new MyEvent("这是自定义事件1"));applicationContext.publishEvent(new MyEvent("这是自定义事件2"));applicationContext.publishEvent(new MyEvent("这是自定义事件3"));}
}
package org.example.listener;import lombok.extern.slf4j.Slf4j;
import org.example.event.MyEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;@Slf4j
@Service
public class MyListener implements ApplicationListener<MyEvent>{@Async//("taskExecutor")@Overridepublic void onApplicationEvent(MyEvent myEvent) {log.info("myEvent:{}",myEvent);log.info("开始死循环");//3个死循环进入默认线程池,而默认线程池核心线程数是3,后续加入的任务都只能放在等待队列,永远没机会执行。while (true){try {Thread.sleep(10000);log.info("myEvent");} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}

通过该线程池名称,搜索到该线程池的配置类:

package org.example.config;import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;@Slf4j
@Configuration
public class ThreadPoolConfig {@Bean//("taskExecutor")public Executor taskExecutor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();//核心线程数executor.setCorePoolSize(3);
//        executor.setCorePoolSize(Runtime.getRuntime().availableProcessors());//最大线程数executor.setMaxPoolSize(5);//队列容量executor.setQueueCapacity(1000);//线程活跃时间(秒)executor.setKeepAliveSeconds(60);//默认线程名称executor.setThreadNamePrefix("taskExecutor-");//拒绝策略executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());//等待所有任务结束后再关闭线程池executor.setWaitForTasksToCompleteOnShutdown(true);
//        executor.initialize();不需要这句代码,因为ThreadPoolTaskExecutor实现了InitializingBean接口,其afterPropertiesSet方法会调用initialize()。return executor;}}

设置线程池的核心线程数使用了系统核心数,在UAT环境刚好是3,而其他环境大于3,这就是到了UAT环境突然有问题的原因了。

通过深入debug,发现spring获取默认线程池,是通过beanName为“taskExecutor”来查找的,而上面自定义线程池, @Bean注解没有指定名称,则取方法名“taskExecutor”,而该名称,碰巧是spring默认线程池的名称,导致该自定义线程池覆盖了spring的默认线程池,从而使用@Async(没有指定名称)都用的该线程池。

最终解决方案可以有2个:

1.把该自定义线程池换个名字,不要跟spring默认线程池名称一样。
2.把该自定义线程池的核心线程数改大点,起码要超过3个。

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

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

相关文章

TIA博途中快速修改变量值的方法和技巧

TIA博途中快速修改变量值的方法和技巧 如下图所示,正常情况下选中该变量,然后右击选择“修改”—然后选择修改为0或1, 快速调试技巧: 如下图所示,鼠标选中该变量上方的“FALSE”,直接双击,系统会提示是否进行切换该变量的值, 点击“是”即可切换变量的值, 如下图所示,…

第三期丨酷雷曼无人机技能培训

第3期无人机技能提升培训 2023年9月15日&#xff0c;第三期酷雷曼合作商无人机技能提升培训圆满举办&#xff0c;数十位来自各地的合作商齐聚北京&#xff0c;在酷雷曼总部的帮助下学习无人机理论及实操知识&#xff0c;并成功取得权威认可的无人机飞行执照。本届培训会的新晋…

quickapp_快应用_DOM元素

DOM $element获取某元素的宽高 $element $element是通用方法(提供给所有组件调用的方法) 获取指定 id 的组件 dom 对象&#xff0c;如果没有指定 id&#xff0c;则返回根组件 dom 对象用法。 this.$element(id名)获取某元素的宽高 const element this.$element(元素id名)…

发明无止境:简单的螺丝钉也有复杂悠久的专利故事?

今天跟大家分享一个螺丝钉专利的故事。 我们从人类开始就有了连接和固定的需求。 最早期的时候&#xff0c;人类就想到了连接和固定最简单的办法就是用钉子把两个物体连接在一起&#xff0c;最早的时候用的是木钉或者楔子。用木钉和楔子的方式简单粗暴&#xff0c;成本也非常的…

【Linux】more命令使用

more 是linux的一个命令&#xff0c;类似cat命令&#xff0c;会以一页一页的显示&#xff0c;方便使用者逐页阅读。 More是一个过滤器&#xff0c;用于一次一屏地对文本进行分页。这个版本特别原始。用户应该意识到&#xff0c;less&#xff08;1&#xff09;提供了更多的模拟…

修改TV app卸载页面选中样式(GuidedStepFragment)

1.源码位置 packages\apps\PackageInstaller2.解决方案 1.继承Theme.Leanback.GuidedStep <style name"Theme.Settings.GuidedStep" parent"style/Theme.Leanback.GuidedStep"><item name"guidedStepBackground">color/settings_…

[传智杯 #3 初赛] 终端

题目描述 有一天您厌烦了电脑上又丑又没用的终端&#xff0c;打算自己实现一个 Terminal。 具体来说&#xff0c;它需要支持如下命令: touch filename&#xff1a;如果名为 filename 的文件不存在&#xff0c;就创建一个这样的文件&#xff0c;如果已经存在同名文件的话则不进…

flutter学习-day2-认识flutter

&#x1f4da; 目录 简介特点架构 框架层引擎层嵌入层 本文学习和引用自《Flutter实战第二版》&#xff1a;作者&#xff1a;杜文 1. 简介 Flutter 是 Google 推出并开源的移动应用开发框架&#xff0c;主打跨平台、高保真、高性能。开发者可以通过 Dart 语言开发 App&#…

百度APP iOS端包体积50M优化实践(七)编译器优化

一. 前言 百度APP iOS端包体积优化系列文章的前六篇重点介绍了包体积优化整体方案、图片优化、资源优化、代码优化、无用类优化、HEIC图片优化实践和无用方法清理&#xff0c;图片优化是从无用图片、Asset Catalog和HEIC格式三个角度做深度优化&#xff1b;资源优化包括大资源…

字符串和内存函数(1)

strcat函数 如上图&#xff0c;strcat函数就是将一个字符串拼接在另一个字符串后面&#xff0c;第一个参数是目标字符串&#xff0c;第二个参数是源字符串&#xff0c;strcat的返回值是目标字符串的起始地址。 注意&#xff1a;1.目标空间必须足够大&#xff0c;还需要可以修改…

【数据仓库-10】-- 数据仓库、数据湖和湖仓一体架构

目录 1 数据仓库与数据库的对比 2 数据湖与数据仓库的对比 3 数据仓库、数据湖和湖仓一体

模型训练 出现NaN的原因以及解决方法

目录 前言1. 原因2. 解决方式 前言 1. 原因 模型训练过程中&#xff0c;修改Loss导致最后的结果出现NaN&#xff0c;一般是因为数值不稳定导致&#xff0c;主要有几个原因&#xff0c;只需要一一排查即可&#xff1a; 学习率过高&#xff1a; 过大的学习率可能导致权重更新过…

QT QStringList类常见用法

0. 实例化方式 QStringList fonts { "Arial", "Helvetica", "Times" }; 1. 三种遍历方式 QStringList fonts { "Arial", "Helvetica", "Times" };// 类STL迭代器for(auto f: fonts){qDebug() << f;}// …

λ表达式、智能指针

lambda 表达式 1、C11标准支持&#xff0c;实现匿名函数的功能&#xff1b; 2、通常用于实现轻量级的函数 格式 mutable->返回值{函数体}; // 返回值即使是 void 也必须得写 [] 内&#xff0c;可以填外部数据&#xff1b; () 内&#xff0c;可以带有参数列表。 lambda 表达…

力扣(LeetCode)1038. 从二叉搜索树到更大和树(C++)

先序遍历 根据题意&#xff0c;给定一个二叉搜索树 root (BST)&#xff0c;请将它的每个节点的值替换成树中大于或者等于该节点值的所有节点值之和。模拟二叉搜索树替换到更大和数的过程&#xff0c; 请了解性质&#xff1a;二叉搜索树的先序遍历&#xff0c;是一个正序数组 …

flutter学习-day3-dart基础

&#x1f4da; 目录 变量声明操作符数据类型控制流错误处理和捕获函数mixin异步 FutureStream 本文学习和引用自《Flutter实战第二版》&#xff1a;作者&#xff1a;杜文 1. 变量声明 var 类似于 JavaScript 中的var&#xff0c;它可以接收任何类型的变量&#xff0c;但最大…

机器学习实验二:决策树模型

系列文章目录 机器学习实验一&#xff1a;线性回归机器学习实验二&#xff1a;决策树模型机器学习实验三&#xff1a;支持向量机模型机器学习实验四&#xff1a;贝叶斯分类器机器学习实验五&#xff1a;集成学习机器学习实验六&#xff1a;聚类 文章目录 系列文章目录一、实验…

linux管道_tee_xargs

5.2 管道 管道命令可以将多条命令组合起来&#xff0c;一次性完成复杂的处理任务。 语法&#xff1a; command1 | command2 | command3...例&#xff1a; 查看passwd中最后3行内容。 cat /etc/passwd | tail -3 查看passwd中包含root所在行的第一条信息。 cat /etc/p…

FPGA串口接收解帧、并逐帧发送有效数据——1

FPGA串口接收解帧、并逐帧发送有效数据 工程实现的功能&#xff1a;FPGA串口接收到串口调试助手发来的数据&#xff0c;将其数据解帧。判断到正确的帧头和帧尾之后&#xff0c;将有效数据存入rx_data中&#xff1b;另一方面发送端将有效数据逐帧发送出去。 参考&#xff1a;正…

【用Python根据用户名和手机号码生成Hash值并创建.cs .h和xlsx文件】

用Python根据用户名和手机号码生成Hash值并创建C Sharp .cs、嵌入式.h和xlsx表格文件 用Python根据用户名和手机号码生成Hash值并创建C Sharp .cs、嵌入式.h和xlsx表格文件&#xff0c;主要是为每个用户创建一个pubkey&#xff0c;并输出C Sharp C#和嵌入式 Keil的工程文件 pub…