SpringBoot定时任务里的多线程
SpringBoot定时任务里的多线程
- 提示
- 前言
- 遇到的问题
- 验证与解决
- 验证
- 单线程执行单任务
- 分析
- 代码及结果
- 单线程执行多任务
- 解决
- 实现单任务的多线程
- 为每个任务创建一个子线程
- 解决多任务的多线程
- 设定固定容量线程池
- 动态设定容量线程池
- 固定线程池和动态线程池的选择
- 简单总结
- 借鉴及引用
提示
本文并不涉及cron表达式,而是关注于执行定时任务的线程。
前言
我们在搭建系统的时候,不可避免地要用到定时任务,注解为 @Scheduled(cron = "cron表达式"),就像闹钟那样,每天定时执行叫醒你起来上早八或者上班的任务。

遇到的问题
在使用过程中,遇到了不少问题。
- 在定时任务中
默认使用单线程的方式来执行定时任务。

也就是任务1、2、3之中只能有任一一个在执行,而无法同时进行。
比如在任务1任务2都是从0秒开始运行,那么在0秒的时候本来预期是两者一起运行,也就是以下这样:
但由于默认是单线程,所以它就可能变成了以下两种情况:
先执行任务1在执行任务2

或者相反

同时执行的任务,在单线程的情况下随机执行一个。
所以你可以看到单线程对于多个任务而言,有很多不方便的地方。比如某个任务耗费时间比较长,那么其他的定时任务就必须要等它执行结束才能继续执行。又或者有的任务直接挂掉了,导致所有的定时任务都无法执行。
- 在
多线程的定时任务中,每个任务只分配到一个线程。
在上面的讨论中,我们注意到单线程的弊端,那么自然有解决办法。那就是多线程执行定时任务,多线程执行任务就是以下这样:

但大家有没有发现一个问题,就是单个线程能应付得了单个任务吗?
举个简单的例子:我们上面的提到过的耗费时间比较长的问题。
比如我们任务1从0秒开始每5秒执行一次,但是其任务执行时间要7秒。那么就可能导致什么问题?大家可以想一下。
3
2
1
你觉得是这样?

但其实是这样:

单线程应付不了单个任务的时候,就可能导致我们的任务并不会按照我们的预期去执行。
如上面那样由于上一次任务的执行时间大于我的定时间隔时间,导致我漏掉下一次任务,又或者某次执行挂掉卡住了导致后续定时任务无法执行。
怎么解决上述的问题?
其实这个问题,可以具体化到如何让任务的每次执行互不影响?
3
2
1
答案揭晓:多线程。
为任务的每次执行分配一个线程,使得每次任务的执行两两独立,互不影响。也就是一下这样:

验证与解决
下面我们就来在项目中验证上述中提到的相关问题,请注意下面的验证顺序与上面提到的问题顺序的略有不同。
验证
以下内容仅仅验证上面的叙述在SpringBoot环境中的定时任务是否准确。
单线程执行单任务
当任务的执行时间,大于你设定的时间时,单线程、单个定时任务如何执行?
以上面提到过的任务一的执行举例:从0秒开始每5秒钟执行一次,但是你的任务每次执行时间需要7秒,请设想一下在一分钟内它最终执行多少次?
分析
第一次执行完是7秒,那么此时已经过了5秒的那个点,所以下一次到10秒才会执行。也就是对应秒数到达0 ,10 ,20,30,40,50的时候才会执行,总共执行6次。也就是上图中的

代码及结果
来看以下定时任务代码:
//从0开始 每五秒执行一次
@Scheduled(cron ="0/5 * * * * *")public void testTread4() throws InterruptedException {//在一分钟内执行if(DateUtil.formatStr(new Date()).compareTo("2023-05-16 17:06") >= 0&& DateUtil.formatStr(new Date()).compareTo("2023-05-16 17:07") < 0) {//睡眠7秒钟Thread.sleep(7000);//执行结束时间System.out.println("执行任务" + DateUtil.formatStr(new Date()));}}
执行结果(注意:这里的输出时间为定时任务执行结束时间而非开始时间):

很明显符合我们预期的结果,也就是单线程情况下,如果在执行任务期间还要执行新的任务,那么该新任务就会被忽略。
单线程执行多任务
在单线程执行多任务情况下,如果某个任务执行过久,由于它是单线程,所以会导致后续任务也被要等待它执行结束,才能继续执行。也就是上图中的

或者

来看以下这段代码的逻辑
//一秒执行一次@Scheduled(cron ="0/1 * * * * *")public void testTread2() {System.out.println("定时任务1 开始 "+ DateUtil.formatStr(new Date()));//假设以下是它需要执行的任务内容 以下代码执行时间超过一秒for(int i =0; i < 1000000 * 500;i++) {String str = "diuhfiahiudhfadfjkakhfkjahsjcndjakdjfoiejalkdjfialdjcxkjhaoifkdhjafhdkahjfkdahfaldkjfianvnjvhdhuiehrkahkjdfakhuidhfakjhducian";String result1 = "";result1 = result1 + str.indexOf(i % 125);}System.out.println("定时任务1 结束 "+ DateUtil.formatStr(new Date()));}//一秒执行一次@Scheduled(cron ="0/1 * * * * *")public void testTread3() {System.out.println("定时任务2 开始 "+ DateUtil.formatStr(new Date()));//假设以下是它需要执行的任务内容 常规时间执行是两秒左右for(int i =0; i < 1000000 * 50;i++) {String str = "diuhfiahiudhfadfjkakhfkjahsjcndjakdjfoiejalkdjfialdjcxkjhaoifkdhjafhdkahjfkdahfaldkjfianvnjvhdhuiehrkahkjdfakhuidhfakjhducian";String result1 = "";result1 = result1 + str.indexOf(i % 125);}System.out.println("定时任务2 结束 "+ DateUtil.formatStr(new Date()));}
我们来看下结果:

可以明显看到,当有任务1未执行完时,即使任务2到达执行的时间,也没有执行。
那么,如何让单线程定时任务变成多线程呢?
画外音:忙碌了一整天的鱼师傅回到家中,发现了这个问题,最复杂的问题往往只需要最朴素的解决办法,他打开了csdn…


不好意思,串台了。
解决
这里需要分为两个方面:
单个任务,多个线程:每次的执行都交给一个新的线程。
对应我们上面提到的:

多个定时任务对应多个线程:让每个任务单独分到一个线程。
也就是上面的这个图

简单来讲,单线程多任务的情况就是,一个定时任务需要多个线程来执行。
多任务多线程就是,多个定时任务需要多个线程来执行。
实现单任务的多线程
将任务的每次执行交给一个新的线程,任务的每次执行独立。
为每个任务创建一个子线程
为每个任务里面的执行内容新建一个子线程。
修改上述代码:
@Scheduled(cron ="0/5 * * * * *")public void testTread4() {//为每一个子任务创建一个新的线程执行 也可以将具体的任务内容封装到一个继承Thread或者实现Runnable接口的类中new Thread(new Runnable() {@Overridepublic void run() {try {//在一分钟内执行if(DateUtil.formatStr(new Date()).compareTo("2023-05-16 17:25") >= 0&& DateUtil.formatStr(new Date()).compareTo("2023-05-16 17:26") < 0) {//睡眠7秒钟Thread.sleep(7000);System.out.println("执行任务" + DateUtil.formatStr(new Date()));}} catch (InterruptedException e) {e.printStackTrace();}}}).start();}
执行结果
//0秒开始执行 执行7秒 07结束
执行任务2023-05-16 17:25:07
//5秒开始执行 12结束
执行任务2023-05-16 17:25:12
//10秒开始执行 17结束
执行任务2023-05-16 17:25:17
执行任务2023-05-16 17:25:22
执行任务2023-05-16 17:25:27
执行任务2023-05-16 17:25:32
执行任务2023-05-16 17:25:37
执行任务2023-05-16 17:25:42
执行任务2023-05-16 17:25:47
执行任务2023-05-16 17:25:52
执行任务2023-05-16 17:25:57
//25:55秒开始执行 26:02结束
执行任务2023-05-16 17:26:02
可以看到执行了12次,并且每个任务的开始时间是00 - 05 - 10 - 15 - .....-55,互不影响,两两独立。
解决多任务的多线程
这个逻辑只需要让每个定时任务各自拥有一个线程,每个定时任务之间独立,你干你的,我干我的,互不影响。
设定固定容量线程池
一种方法是添加配置定时任务的配置类,修改默认的线程池容量(默认为1,也就是单线程)。
新建ScheduleConfig.java配置文件,内容如下:
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.stereotype.Component;@Component
public class ScheduleConfig {@Beanpublic TaskScheduler taskScheduler() {//定时任务线程池ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();// 设置线程池大小为10taskScheduler.setPoolSize(10);return taskScheduler;}
}
执行结果:

可以看到任务1和任务2同时开始执行,不再出现谁等谁的情况了。
动态设定容量线程池
另一种方法是实现任务调度配置接口 – SchedulingConfigurer ,根据任务数量配置线程池钟线程最大数量,也可以达到上面的效果。
@Component
@Configuration
@EnableScheduling
public class MySchedule//需要实现这个接口implements SchedulingConfigurer {//配置定时任务线程池信息 有多少定时任务就配置多大的线程池 任务中新建的子线程并不由是此线程池里的对象@Overridepublic void configureTasks(ScheduledTaskRegistrar taskRegistrar) {//获取所有方法Method[] methods = MySchedule.class.getMethods();//线程池默认大小10int defaultPoolSize = 10;//实际大小为0int corePoolSize = 0;Assert.notNull(methods , "并无定时任务");if (methods.length > 0) {//遍历所有任务 统计所有带定时任务注解的方法个数for (Method method : methods) {Scheduled annotation = method.getAnnotation(Scheduled.class);if (annotation != null) {corePoolSize++;}}//最小为10if (defaultPoolSize > corePoolSize)corePoolSize = defaultPoolSize;}System.out.println("线程池大小:" + corePoolSize);taskRegistrar.setScheduler(Executors.newScheduledThreadPool(corePoolSize));}//一秒执行一次@Scheduled(cron ="0/1 * * * * *")public void testTread2() {System.out.println("定时任务1 开始 "+ DateUtil.formatStr(new Date() , DateUtil.MillisecondPattern));//假设以下是它需要执行的任务内容 以下代码执行时间超过一秒for(int i =0; i < 1000000 * 500;i++) {String str = "diuhfiahiudhfadfjkakhfkjahsjcndjakdjfoiejalkdjfialdjcxkjhaoifkdhjafhdkahjfkdahfaldkjfianvnjvhdhuiehrkahkjdfakhuidhfakjhducian";String result1 = "";result1 = result1 + str.indexOf(i % 125);}System.out.println("定时任务1 结束 "+ DateUtil.formatStr(new Date() , DateUtil.MillisecondPattern));}//一秒执行一次@Scheduled(cron ="0/1 * * * * *")public void testTread3() {System.out.println("定时任务2 开始 "+ DateUtil.formatStr(new Date() , DateUtil.MillisecondPattern));//假设以下是它需要执行的任务内容 常规时间执行是两秒左右for(int i =0; i < 1000000 * 50;i++) {String str = "diuhfiahiudhfadfjkakhfkjahsjcndjakdjfoiejalkdjfialdjcxkjhaoifkdhjafhdkahjfkdahfaldkjfianvnjvhdhuiehrkahkjdfakhuidhfakjhducian";String result1 = "";result1 = result1 + str.indexOf(i % 125);}System.out.println("定时任务2 结束 "+ DateUtil.formatStr(new Date() , DateUtil.MillisecondPattern));}
}
执行结果

可以看到两个任务你干你的 我干我的,不再出现,我要等你干完,我才能干的情况。
需要注意的是,这里设定的池是全局通用的,任何定时任务都可以用这个池,而且共用一个。如果存在多个执行定时任务的类,并且每个类中都有上述的构建池的操作,那么会以最后执行的设置池的容量为准。(这个大家可以自己测试一下,输出的时候加上Thread.currentThread().getName()这个 – 获取线程名称,用于区分线程池里的线程即可。)
比如我有两个定时任务类,第一个里面有2个定时任务,第二个里面有18个定时任务:

那么执行的时候MySchedule设置的池大小为10,West01设置的池大小为18。最后各个任务执行的时候,所用到的池大小为18。
如果将West01更改为Aest01,那么最后执行用到的池就是MySchedule里面设置的池容量,大小为10。
因为按照名称排序并且加载,MySchedule的加载排在Aest01之后,West01之前,容量以最后的设置为准。
MySchedule与West01

MySchedule与Aest01

固定线程池和动态线程池的选择
如果你所有的定时任务都在一个类里面,毫无疑问第二种动态线程池就是最佳选择。
但是你有多个定时任务类,选择第一种完全是ok的,但计算估计有点麻烦需要计算一下,设置为多少合理。
简单总结
在文中,我们从简单的原理描述,到验证,再到最后实操;
从默认的单线程执行任务到多线程执行单任务,再到多线程执行多任务。
想必你会觉得“多线程这么爽那还用什么单线程?”但其实多线程的问题也不少,简单对比一下:
- 单线程占用资源
少,多线程占用资源多。在多线程情况下,如果某些任务执行时间过长,而新的任务又在不断地产生新的进程,将会迅速耗尽计算机资源。 单线程不需要考虑资源竞争,多线程需要对共享资源做对应的处理。这个比较容易理解。比如买票,有两个售票员卖票,现在只剩下一张票,这时他们同时接到顾客,都告诉顾客有票,但实际操作的时候手速快的那个把票拿走了,那么后者就没有票可卖了。- 单线程
日志记录简单、清楚,多线程日志记录混乱。前者是按顺序说话,后者是这里来一句,那里来一句。
就说到这个里了,还是那句话,没有最好的解决方法,只有最合适的解决方法。
借鉴及引用
Java 多线程编程基础(详细)
Springboot @Scheduled定时任务单线程执行问题
相关文章:
SpringBoot定时任务里的多线程
SpringBoot定时任务里的多线程 提示前言遇到的问题验证与解决验证单线程执行单任务分析代码及结果 单线程执行多任务 解决实现单任务的多线程为每个任务创建一个子线程 解决多任务的多线程设定固定容量线程池动态设定容量线程池固定线程池和动态线程池的选择 简单总结借鉴及引用…...
YOLO V3 SPP ultralytics 第二节:根据yolo的数据集,生成准备文件和yolo的配置文件
目录 1. 介绍 2. 完整代码 3. 代码讲解 3.1 生成 my_train_data.txt和my_val_data.txt 3.2 生成 my_data.data 文件 3.3 生成 my_yolov3.cfg 3.4 关于my_data_label.names文件 1. 介绍 根据 第一节 的操作,已经生成了下图中圆圈中的部分,而本…...
camunda流程引擎connector如何使用
在 Camunda 中,Connector 是一种用于与外部系统或服务交互的机制。它允许 BPMN 模型中的 Service Task 节点与外部系统或服务进行通信,从而使流程更加灵活和可扩展。使用 Connector,可以将业务流程与外部系统集成在一起,而无需编写…...
ECO基本概念:pre-mask eco gen patch flow
使用conformal LEC 进行pre-mask eco 时,如何产生patch,参考以下步骤: 官方推荐 Flattened ECO Flow(FEF) Conformal支持Flattened ECO Flow和Hierarchical ECO Flow。Flattened下,工具会将 ECO 分析重点…...
【初学人工智能原理】【4】梯度下降和反向传播:能改(下)
前言 本文教程均来自b站【小白也能听懂的人工智能原理】,感兴趣的可自行到b站观看。 本文【原文】章节来自课程的对白,由于缺少图片可能无法理解,故放到了最后,建议直接看代码(代码放到了前面)。 代码实…...
微信小程序路由传参
微信小程序路由传参 在微信小程序中,可以通过路由传参将数据传递给目标页面。以下是一种常见的方式: 在源页面中,使用 wx.navigateTo 或 wx.redirectTo 方法跳转到目标页面,并通过 URL 参数传递数据。示例: wx.navi…...
深入篇【C++】类与对象:再谈构造函数之初始化列表与explicit关键字
深入篇【C】类与对象:再谈构造函数之初始化列表与explicit关键字 Ⅰ.再谈构造函数①.构造函数体赋值②.初始化列表赋值【<特性分析>】1.至多性2.特殊成员必在性3.必走性:定义位置4.一致性5.不足性 Ⅱ.explicit关键字①.隐式类型转化②.作用 Ⅰ.再谈…...
广东棒球发展建设·棒球1号位
一、概述 棒球是一项源于美国的运动,自20世纪初开始传入中国,近年来在广东省的发展也逐渐受到关注。本文将就广东棒球的发展现状及未来发展方向进行分析。 二、发展现状 目前广东省内棒球赛事主要有以下几种: 1. 业余棒球联赛:…...
浅谈PMO对组织战略的支持︱美团骑行事业部项目管理中心负责人边国华
美团骑行事业部项目管理中心负责人边国华先生受邀为由PMO评论主办的2023第十二届中国PMO大会演讲嘉宾,演讲议题:浅谈PMO对组织战略的支持。大会将于6月17-18日在北京举办,更多内容请浏览会议日程 议题内容简要: 战略是组织运行的…...
互联网医院资质代办|互联网医院牌照的申请流程
随着互联网技术的不断发展,互联网医疗已经逐渐成为人们关注的热点话题。而互联网医院作为互联网医疗的一种重要形式,也越来越受到社会各界的关注。若想开展互联网医院业务,则需要具备互联网医院牌照。那么互联网医院牌照的申请流程和需要的资…...
网络:DPDK复习相关知识点_2
1.RTC运行至完成时模式,单核单模块 2.pipeline模式,多核多模块,每个模块都是一个处理引擎,但会有缓存一致性问题 3.Mbuff数据包内存操作对象,相当于是数据包的一个索引,对网络的处理都集中在这个Buff上 …...
阿里云大学考试Java中级题目及解析-java中级
阿里云大学考试Java中级题目及解析 1.servlet释放资源的方法是? A.int()方法 B.service()方法 C.close() 方法 D.destroy()方法 D servlet释放资源的方法是destroy() 2.order by与 group by的区别? A.order by用于排序,group by用于排序…...
【星戈瑞】Sulfo-CY3-COOH磺化/水溶性Cyanine3羧酸1121756-11-3
Sulfo-CY3 COOH是一种荧光染料,其分子结构中含有COOH官能团,最大吸收波长为550纳米左右,可以通过分光光度计等设备进行检测。Sulfo-CY3 COOH是一种带有羧基的荧光染料,可以与含有氨基的生物分子通过偶联反应形成共价键,…...
Java NIO和IO的主要区别
当学习了Java NIO和IO的API后,一个问题马上涌入脑海: 我应该何时使用IO,何时使用NIO呢?在本文中,我会尽量清晰地解析Java NIO和IO的差异、它们的使用场景,以及它们如何影响您的代码设计。 下表总结了Java N…...
SQL查询语句
DQL语句--排序查询 # 格式: select * from 表名 order by 要排序的列1 [asc/desc], 要排序的列2 [asc/desc]; # 解释: # 1. 无论SQL语句简单或者是复杂, order by语句一般都放最后, 注意: 如果有limit(分页), 则它(limit)在最后. # 2. asc表示升序, desc表示降序, 其中, 默…...
四象限法进程调度
周二收到一篇推送 一次云上网络毫秒级的优化与实践,很有意义的实践和探索,建议阅读,文章不长,没有冗长的源码分析,结论很清晰。 谈谈我的看法。 多少有种感觉,Linux 越来越像个响应系统而不是服务器。 虚…...
蓝桥杯拿到一等奖,并分享经验
昨天和群里的小伙伴在群里聊,有的小伙伴竟然说蓝桥杯一等奖没有含量,我也是醉了! 就像去年看了一个号主写的:研究生遍地都是! 放眼全国14亿人口,别说研究生了,本科生占比有多少? “蓝桥杯是我人生中得到…...
vue3。 Cannot use JSX unless the ‘–jsx’ flag is provided. ts(17004)
react用tsx或者jsx很常见,也有配套的配置 那如果是vue呢? 默认是没问题的,可是我用了jsdoc,并开启了checkjs,然后vscode就爆红了 谷歌,百度,一个晚上 查到的答案: 推荐我新增tsco…...
HVV面试题目总结
蓝队 如何识别安全设备中的无效告警? 常见的端口有哪些? 这些端口对应的服务是什么? 针对这些服务,红队攻击方式有哪些? 常用的威胁情报平台有哪些? 有没有做过关于情报输出的工作? 木马驻留系统的方式有哪些? 当收到钓鱼邮件的时候,说说处置思路…...
Access denied for user ‘root‘@‘localhost‘ (using password:YES) 解决方案
文章目录 问题描述解决方案: 问题描述 Access denied for user ‘root’‘localhost’:拒绝用户’root’localhost’的访问。 出现这个报错语句的一般原因是输入了错误的密码,也有可能是是root帐户默认不开放远程访问权限。 相关的解决方法是重新设置…...
全链路压测实战:双十一级别的流量,我是这样扛住的
作为一名在质量保障领域摸爬滚打多年的测试工程师,我深知传统的单接口压测在如今分布式架构下的无力感。当业务流量达到双十一这种脉冲式、高并发的级别时,任何一个非核心链路上的“短板”都可能引发系统性的雪崩。全链路压测不再是选择题,而…...
第三卷第4章:原型模式设计思想
第三卷第4章:原型模式设计思想 目录介绍 01.案例引入与思考 1.1 痛点场景 1.2 它哪里不舒服 1.3 引出本篇主角 02.原型模式介绍 2.1 原型模式由来 2.2 原型模式定义...
真可用!美团数字人模型开源,MV、电商等统统拿下
美团开源的数字人视频生成框架 LongCat-Video-Avatar 刚刚更新到 1.5 版本。是真能用。这版更新把音频编码器换了,推理步数砍到8步,在770人、13240条主观评分的大规模评测里,雷达图面积全面领先。音频编码器换血,8步出图LongCat-V…...
告别漫长等待:UE5.2.1 Windows打包效率优化与插件问题排查指南
告别漫长等待:UE5.2.1 Windows打包效率优化与插件问题排查指南第一次点击"打包项目"按钮时,进度条仿佛被冻结的场景,每个UE5开发者都经历过。尤其当项目规模达到数十GB时,等待时间可能超过一小时——这背后隐藏着引擎底…...
如何快速上手Redux Dynamic Modules:5分钟完成Redux模块化改造
如何快速上手Redux Dynamic Modules:5分钟完成Redux模块化改造 【免费下载链接】redux-dynamic-modules Modularize Redux by dynamically loading reducers and middlewares. 项目地址: https://gitcode.com/gh_mirrors/re/redux-dynamic-modules Redux Dyn…...
利用 Taotoken 多模型能力为智能客服场景提供备份路由
🚀 告别海外账号与网络限制!稳定直连全球优质大模型,限时半价接入中。 👉 点击领取海量免费额度 利用 Taotoken 多模型能力为智能客服场景提供备份路由 智能客服系统是许多企业与用户交互的关键入口,其响应能力和服务…...
基于BLE模块的低功耗无线遥控器设计与实现
1. 项目概述:基于BLE模块的无线遥控器设计与实现几年前,我在捣鼓智能家居时,一直想找一个低功耗、响应快、又能自己完全掌控的无线遥控方案。市面上的成品要么协议封闭,要么功耗感人,要么延迟高得让人着急。后来&#…...
如何用OpenHRMS打造企业级人力资源管理系统:30+模块完全指南
如何用OpenHRMS打造企业级人力资源管理系统:30模块完全指南 【免费下载链接】OpenHRMS 项目地址: https://gitcode.com/gh_mirrors/op/OpenHRMS 还在为繁琐的人力资源管理头疼吗?🤔 面对员工考勤、薪酬计算、绩效评估等复杂流程&…...
DRG存档编辑器终极指南:如何快速解锁《深岩银河》的全部游戏体验
DRG存档编辑器终极指南:如何快速解锁《深岩银河》的全部游戏体验 【免费下载链接】DRG-Save-Editor Rock and stone! 项目地址: https://gitcode.com/gh_mirrors/dr/DRG-Save-Editor 还在为《深岩银河》中无尽的资源收集和等级提升感到疲惫吗?DRG…...
《关于 AI Agent 基础设施的一些奇思妙想》
目录 目录 目录 一、AI Agent 容器 问题背景 想法思路:API 中转站模式 多 Agent 切换 二、手机端操控 AI Agent(手机与电脑互联) 三、AI 开发依赖管理工具 总结 最近 AI Agent 越来越火,我作为一个重度使用者,…...
