SpringBoot之定时任务

1. 前言

        本篇博客是个人的经验之谈,不是普适的解决方案。阅读本篇博客的朋友,可以参考这里的写法,如有不同的见解和想法,欢迎评论区交流。如果此篇博客对你有帮助,感谢点个赞~

2. 场景

        我们讨论在单体项目,单个实例中的定时任务相关问题。暂时先不讨论单体项目多副本的情况,也不讨论分布式定时任务。针对分布式定时任务,下一篇博客中再详细讨论。

        开发中遇到的场景是:一个单体项目,就比如一个后台管理系统需要多个定时任务去做一些业务处理。比如如下两个定时任务(以下定时任务是随便写的,目的是模拟系统中存在不同的定时任务,不需要纠结任务的合理性):

        (1)每隔10秒从网络上获取某些产品信息;

        (2)每隔3秒统计新注册的用户;

3. 需求实现

3.1 实现方式一

3.1.1 代码

        有了如上两个定时任务的需求,首先想到了SpringBoot中可以使用 @Scheduled 快速开启一个定时任务。代码如下:

import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;@EnableScheduling
@Component
@Slf4j
public class MyJob {// 每隔2秒从网络上获取某些产品信息@Scheduled(cron = "*/2 * * * * ?")public void getProductInfo() throws InterruptedException {log.info("定时任务 - 从网络上获取某些产品信息");// 模拟业务耗时操作Thread.sleep(5000);}// 每隔3秒统计新注册的用户@Scheduled(cron = "*/3 * * * * ?")public void userJob() throws InterruptedException {log.info("定时任务 - 统计新注册的用户");// 模拟业务耗时操作Thread.sleep(1000);}}

 3.1.2 结果分析

        对上述定时任务的执行结果进行分析如下:

3.2 实现方式二

3.2.1 代码

        在上述 <实现方式一> 的基础上进行改进,给每次触发的定时任务分配一个线程去执行。注意:是每次触发定时任务时,给其分配一个线程,不要理解成给某个定时任务方法单独分配一个线程。再详细一些的解释:比如 "获取产品信息" 这个定时任务方法,每隔2秒触发一次,我们要实现的目标是:每隔2秒触发时,都为其分配一个线程去执行。

        要实现上述需求,可以定义一个线程池,每次定时任务触发时,从线程池中分配一个线程去执行当前的定时任务。我们可以把线程池的一些配置信息放到配置文件中,如下是自定义线程池的代码:

        3.2.1.1 线程池的配置

        在配置文件中指定线程池参数的配置信息,在实际生产环境中,如果需要修改线程池参数配置,修改配置文件即可。

# 自定义线程池相关配置(这里的线程池参数配置需要根据不同系统进行制定,这里只是一个示例)
custom:thread:core-pool-size: 20maximum-pool-size: 50queue-capacity: 10000keep-alive-time: 10name-prefix: "scheduled-thread-"
         3.2.1.2 配置类

        和配置文件中以 customer.thread 开头的配置信息进行绑定

package com.shg.distributed.lock.component;import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;/*** @DESCRIPTION: 和自定义线程池相关的配置项,比如配置线程池的核心线程数* 最大线程数、阻塞队列的容量大小、非核心线程的存活时间等* @USER: shg* @DATE: 2024/06/24*/
@Data
@ConfigurationProperties(prefix = "custom.thread")
public class ThreadPoolConfigProperties {private int corePoolSize;private int maximumPoolSize;private int queueCapacity;private int keepAliveTime;private String namePrefix;}
        3.2.1.3  自定义线程池类
package com.shg.distributed.lock.component;import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;@Configuration
@EnableConfigurationProperties({ThreadPoolConfigProperties.class})
public class ScheduledThreadPool {private final ThreadPoolConfigProperties threadPoolConfigProperties;public ScheduledThreadPool(ThreadPoolConfigProperties threadPoolConfigProperties) {this.threadPoolConfigProperties = threadPoolConfigProperties;}@Bean(name = "asyncServiceExecutor")public Executor asyncServiceExecutor() {//阿里巴巴编程规范:线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。//SpringBoot项目,可使用Spring提供的对 ThreadPoolExecutor 封装的线程池 ThreadPoolTaskExecutor:ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();//配置核心线程数executor.setCorePoolSize(threadPoolConfigProperties.getCorePoolSize());//配置最大线程数executor.setMaxPoolSize(threadPoolConfigProperties.getMaximumPoolSize());//配置队列大小executor.setQueueCapacity(threadPoolConfigProperties.getQueueCapacity());// 非核心线程的存活时间executor.setKeepAliveSeconds(threadPoolConfigProperties.getKeepAliveTime());//配置线程池中的线程的名称前缀executor.setThreadNamePrefix(threadPoolConfigProperties.getNamePrefix());// rejection-policy:当pool已经达到max size的时候,如何处理新任务//     1、CallerRunsPolicy:不在新线程中执行任务,而是由调用者所在的线程来执行。//        "该策略既不会抛弃任务,也不会抛出异常,而是将任务回推到调用者。"顾名思义,在饱和的情况下,调用者会执行该任务(而不是由多线程执行)//     2、AbortPolicy:拒绝策略,直接拒绝抛出异常//executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());//执行初始化executor.initialize();return executor;}
}
3.2.1.4 主启动类 

        主启动类上主要是标注:@EnableAsync 和 @EnableScheduling注解

package com.shg.distributed.lock;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
@EnableAsync
@EnableScheduling
@SpringBootApplication
public class DistributedLockApplication {public static void main(String[] args) {SpringApplication.run(DistributedLockApplication.class, args);}}

3.2.2 结果分析 

        对上述定时任务的执行结果分析如下:

4. 存在的问题

4.1 问题分析

        现在思考一个问题:"获取产品" 这个定时任务触发后,执行业务需要耗时5秒钟,但是定时任务是每隔2秒执行一次。如果每次定时任务被触发执行时,都为其分配一个线程。则会出现如下情况:

(1)15:21:50秒时刻触发一次定时任务,从线程池中拿一个线程 T1 开始处理业务逻辑;


(2)15:21:52秒时刻又触发执行定时任务,从线程池中拿一个线程 T2 开始处理业务逻辑(此时肯定拿不到线程 T1,因为 T1 线程还在处理第一个定时任务);


(3)15:21:54秒时刻又触发执行定时任务,从线程池中拿一个线程 T3 开始处理业务逻辑(此时肯定拿不到线程 T1和T2,因为 T1 和 T2线程都在处理各自的定时任务中的业务逻辑);


(4)15:21:55秒时刻,T1线程的业务逻辑处理完毕,T1线程释放,归还给线程池


(5)15:21:56秒时刻又触发执行定时任务,从线程池中拿一个线程 T4 开始处理业务逻辑(此时肯定拿不到线程T2 和 T3, 因为 T2 和 T3线程都在处理各自的定时任务中的业务逻辑);

        代码示例和输出结果如下:

4.2 问题解决 

        上面分析了 <当定时任务触发的时间间隔比处理业务耗时要小> 这种情况。大白话解释就是:上一个定时任务还没执行完成,下一个定时任务又开始了。

        而在实际的业务逻辑中,当一个定时任务触发执行后,下一次定时任务需要等到上一个定时任务执行完毕之后,才能开始执行。

        此时,我们就可以在定时任务触发时,为其加一把锁,如果成功获取到锁,则开始执行,执行完毕之后,就释放锁。如果在一个定时任务执行过程中,又触发了一次定时任务,此时是获取不到锁的,这个定时任务就不会执行业务逻辑了。

        进一步说明加锁后的效果,让打印信息更详细一些,注意图中每对【开始】-【结束】之间相差刚好是5秒。如下图: 

5. 总结

        以上我们从一个需求出发,讨论了
        (1)直接使用@Scheduled注解开启一个定时任务的方式及其遇到的问题;
        (2)接着针对(1)中的问题,我们自定义了线程池,让每次触发的定时任务,在不同的线程中执行,避免了一个项目中多个定时任务都在同一个线程中执行,导致定时任务阻塞的问题;
        (3)针对(2),如果一个定时任务没有执行完毕,下一个定时任务又开启了这种不合理的逻辑,我们通过简单的加锁方式解决了此问题。

        但是要注意,这里我们只是讨论并解决了 【单体项目-部署单个实例】中的定时任务的一些问题,并没有解决【单体项目-多副本实例】和【分布式项目中定时任务】的定时任务相关问题。
接下来的博客会对【单体项目-多副本实例】和【分布式项目中定时任务】进行讨论。主要讨论如下问题:

        (1)如何给定时任务添加分布式锁;
        (2)如何给定时任务添加的分布式锁进行续期;
        (3)其他一些衍生问题...

如果此篇文章对你有些许启发和帮助,感谢点个赞~

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

【日志】力扣58.最后一个单词的长度//14.最长公共前缀//28. 找出字符串中第一个匹配项的下标

2024.11.6 【力扣刷题】 58. 最后一个单词的长度 - 力扣&#xff08;LeetCode&#xff09;https://leetcode.cn/problems/length-of-last-word/?envTypestudy-plan-v2&envIdtop-interview-150 int lengthOfLastWord(char* s) {int count 0;for (int i strlen(s) - 1; i…

智能家居的未来:AI让生活更智能还是更复杂?

内容概要 智能家居的概念源于将各种家居设备连接到互联网&#xff0c;并通过智能技术进行控制和管理。随着人工智能的迅速发展&#xff0c;这一领域也迎来了前所未有的机遇。从早期简单的遥控器到如今可以通过手机应用、语音助手甚至是环境感应进行操作的设备&#xff0c;智能…

1. 初步认识 Java 虚拟机

一、前言 其实一直都想系统性的学习一下 JVM&#xff0c;尝试过很多次&#xff0c;最终没能坚持下来&#xff0c;现在已经工作多年&#xff0c;发现对于 JVM这块知识还是很薄弱&#xff0c;不利于职业长远发展&#xff0c;并且之前掌握的都是一些零散的知识&#xff0c;没能形…

数据结构之二叉树的链式结构——递归的暴力美学

1. 实现链式的二叉树结构 我们之前用顺序表里面数组的底层结构实现了二叉树中堆的结构&#xff0c;但是不是所有的二叉树都具有着堆的性质&#xff0c;所以我们现在需要一个链式结构来描述普遍的二叉树。其底层结构类似一个链表&#xff0c;但是每一个结点由单个区域&#xff…

计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-10-31

计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-10-31 目录 文章目录 计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-10-31目录1. Large Language Models for Manufacturing摘要创新点算法模型实验效果&#xff08;包含重要数据与结论&#xff09;推荐…

利用SpringBoot构建城镇住房保障平台

1系统概述 1.1 研究背景 随着计算机技术的发展以及计算机网络的逐渐普及&#xff0c;互联网成为人们查找信息的重要场所&#xff0c;二十一世纪是信息的时代&#xff0c;所以信息的管理显得特别重要。因此&#xff0c;使用计算机来管理城镇保障性住房管理系统的相关信息成为必然…

【笔记】扩散模型(九):Imagen 理论与实现

论文链接&#xff1a;Photorealistic Text-to-Image Diffusion Models with Deep Language Understanding 非官方实现&#xff1a;lucidrains/imagen-pytorch Imagen 是 Google Research 的文生图工作&#xff0c;这个工作并没有沿用 Stable Diffusion 的架构&#xff0c;而是级…

Windows下载安装Ollama本地运行大模型,新手详细

目录 1. 下载安装Ollama2. 环境配置- 关闭开机自启动&#xff08;可选&#xff09;&#xff1a;- 配置环境变量&#xff08;必须&#xff09;&#xff1a;- 配置端口&#xff08;可选&#xff09;&#xff1a;- 允许浏览器跨域请求&#xff08;可选&#xff09;&#xff1a; 3.…

代码随想录算法训练营Day55 | 图论理论基础、深度优先搜索理论基础、卡玛网 98.所有可达路径、797. 所有可能的路径、广度优先搜索理论基础

目录 图论理论基础 深度优先搜索理论基础 卡玛网 98.所有可达路径 广度优先搜索理论基础 图论理论基础 图论理论基础 | 代码随想录 图的基本概念 图的种类 大体分为有向图和无向图。 图中的边有方向的是有向图&#xff1a; 图中的边没有方向的是无向图&#xff1a; 图…

牛客练习赛131(dp,dfs,bfs,线段树维护等差数列)

文章目录 牛客练习赛131&#xff08;dp&#xff0c;dfs&#xff0c;bfs&#xff0c;线段树维护等差数列&#xff09;A. 小H学语文B. 小H学数学&#xff08;dp、偏移值&#xff09;C. 小H学生物&#xff08;DFS、树上两点间路径的距离&#xff09;D. 小H学历史(BFS)E. 小H学物理…

干货分享篇:Air780EP的硬件设计原理全解析(上)

一、绪论 Air780EP是一款基于移芯EC718P平台设计的LTE Cat 1无线通信模组。支持FDD-LTE/TDD-LTE的4G远距离无线传输技术。另外&#xff0c;模组提供了USB/UART/I2C等通用接口满足IoT行业的各种应用诉求。 二、综述 2.1 型号信息 表格 1&#xff1a;模块型号列表 2.2 主要性能…

Python将Word文档转为PDF

将word转pdf&#xff0c;只能使用办公工具&#xff0c;但是这些工具大都是收费。因此想用python 将word转pdf,发现很好用特此记录下。方法一&#xff1a;使用docx2pdf模块将docx文件转为pdf 要实现这样的功能&#xff0c;需要用到的就是 docx2pdf 这个python第三方库。对于doc…

无惧任天堂的法律威胁:Switch模拟器Ryujinx v1.2.72版发布

此前任天堂向多个提供 Nintendo Switch 模拟器项目发送律师函甚至直接起诉&#xff0c;要求这些项目立即停止更新、删除以及向任天堂提供经济赔偿。其中 Ryujinx 项目已经在 2024 年 10 月 1 日因任天堂的法律威胁而放弃项目&#xff0c;不过很快就有分叉版本出现&#xff0c;这…

JavaWeb——Web入门(6/9)-HTTP协议:协议解析(客户端的 HTTP 协议解析、服务端的 HTTP 协议解析、Web服务器的作用)

目录 概述 客户端的 HTTP 协议解析 服务端的 HTTP 协议解析 Web服务器的作用 概述 了解完 HTTP 协议的请求数据格式以及响应数据格式之后&#xff0c;接下来我们来讲了解 HTTP 协议的解析。 HTTP 协议的解析分为客户端和服务端两个部分&#xff0c;客户端浏览器中内置了解…

操作系统-实验报告单(2)

目录 1 实验目标 2 实验工具 3 实验内容、实验步骤及实验结果 一、自定义操作系统并启动 1. 最简单操作系统的编写并生成镜像文件 2.虚拟机启动操作系统 【思考题&#xff1a;1、仔细阅读helloos.nas&#xff0c;结合操作系统启动过程尝试分析它的作用&#xff1b;2、若…

城镇住房保障:SpringBoot系统优化技巧

3系统分析 3.1可行性分析 通过对本城镇保障性住房管理系统实行的目的初步调查和分析&#xff0c;提出可行性方案并对其一一进行论证。我们在这里主要从技术可行性、经济可行性、操作可行性等方面进行分析。 3.1.1技术可行性 本城镇保障性住房管理系统采用SSM框架&#xff0c;JA…

FlyMcu串口下载STLink Utility

1、FlyMcu FlyMcu串口下载&#xff0c;同STC-ISP&#xff08;51单片机下载&#xff09;。 使用步骤&#xff1a; 1、STM32的USART1通过串口转usb连接到电脑 2、通过keil生成Hex、bin文件 生成bin、hex文件可参考 keil生成bin文件&#xff08;简单&#xff09;-CSDN博客 创建…

aws(学习笔记第十课) 对AWS的EBS如何备份(snapshot)以及使用snapshot恢复数据,AWS实例存储

aws(学习笔记第十课) 对AWS的EBS如何备份&#xff08;snapshot&#xff09;以及使用snapshot&#xff0c;AWS实例存储 学习内容&#xff1a; 对AWS的EBS如何备份AWS实例存储EBS和实例存储的不足 1. 对AWS的EBS如何备份&#xff08;snapshot&#xff09;以及使用snapshot恢复数…

论文2—《基于柔顺控制的智能神经导航手术机器人系统设计》文献阅读分析报告

论文报告&#xff1a;基于卷积神经网络的手术机器人控制系统设计 摘要 本研究针对机器人辅助微创手术中定向障碍和缺乏导航信息的问题&#xff0c;设计了一种智能控制导航手术机器人系统。该系统采用可靠和安全的定位技术、7自由度机械臂以及避免关节角度限制的逆运动学控制策…

《数据结构与算法》二叉树基础OJ练习

二叉树的基础知识详见&#xff1a;《数据结构与算法》二叉树-CSDN博客 1 单值二叉树 思路 我们把树分成当前树&#xff08;用根和左孩子还有右孩子进行比较&#xff0c;如果左孩子或者右孩子为空那就不比了&#xff0c;如果左右孩子或者其中一个存在就比较&#xff0c;相等就是…