IDEA SpringBoot实现定时任务(保姆级教程,超详细!!!)

news2024/11/14 3:10:07

目录

1. 前言

2. 创建SpringBoot项目

3. Maven依赖引入 

4. 创建定时任务

5. 问题:执行时间延迟和单线程执行

5.1 问题原因 

5.2 解决方式


1. 前言

在现代化应用中,定时任务(Scheduled Tasks)是不可或缺的一部分,它们允许开发者在预定的时间间隔内自动执行特定任务,如数据同步、报告生成、系统维护等。Spring Boot作为一款流行的Java开发框架,提供了简洁而强大的定时任务实现方式。

Spring Boot通过内置的@Scheduled注解和@EnableScheduling配置,使得开发者能够轻松地在应用中集成定时任务功能。无需引入额外的依赖或复杂的配置,只需在方法上添加@Scheduled注解,并指定任务的执行计划,Spring Boot便会自动处理任务的调度和执行。

本教程将带您逐步了解Spring Boot定时任务的实现过程,包括基本的配置和注解使用、常见的执行计划设置、以及高级功能如动态定时任务、多线程定时任务等。通过本教程的学习,您将能够掌握在Spring Boot中高效实现定时任务的方法和技巧,为您的应用增添更多自动化和智能化的功能。

请注意,随着技术的不断发展,Spring Boot的版本和特性也在不断更新。因此,建议参考最新的Spring Boot官方文档和相关教程来获取最准确的信息。同时,本教程也提供了实用的示例和代码,帮助您更好地理解和应用定时任务功能。

2. 创建SpringBoot项目

首先,我们需要创建SpringBoot项目。在创建SpringBoot项目时,可以选择使用Spring Initializr来快速生成项目结构。

创建SpringBoot项目教程,本文就不过多讲解了,具体操作可参考往期博文:

IDEA创建SpringBoot项目教程,讲解超详细(2024最新)!!!

3. Maven依赖引入 

在Spring Boot项目中使用@Scheduled注解实现定时任务时,你通常不需要额外导入特定的依赖,因为@Scheduled是Spring框架的核心功能之一,并且Spring Boot会自动配置与调度相关的组件。

但是,确保你的Spring Boot项目包含了Spring Boot的起步依赖(starter dependencies),特别是spring-boot-starter或与你项目相关的特定starter依赖(比如spring-boot-starter-web用于Web应用),这些starter依赖会包含使用@Scheduled所需的所有必要组件。

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

4. 创建定时任务

@Slf4j
@Component
@EnableScheduling
public class DemoTask {

    // 每10秒执行一次,cron的表达式暂时不多说明了,可以自行百度学习
    @Scheduled(cron = "0/10 * * * * ? ")
    public void testSchedule() {
        log.info("当前执行任务的线程号ID===>{}", Thread.currentThread().getId()); // 日志输出
    }

}

注释讲解

 @Slf4j:Lombok库提供的一个注解,用于在Java类中自动生成一个名为log的日志对象。通过使用@Slf4j注解,你可以直接在代码中调用log对象的方法来记录不同级别的日志,如log.info(), log.debug(), log.error() 等。这些日志方法可以帮助你记录应用程序的运行状态、调试信息、警告和错误等,从而有助于排查问题、跟踪程序运行情况,提高系统稳定性和可维护性。

@Component:Spring框架中的一个核心注解,它用于将一个类标记为Spring上下文中的一个组件。当一个类被标记为@Component时,Spring容器会在启动时自动扫描并实例化这个类,并将其注册到Spring上下文中。这个类就成为了Spring上下文中的一个bean,可以被其他bean通过依赖注入的方式使用。

@EnableScheduling:Spring框架提供的一个注解,用于启用基于注解的定时任务调度功能。当在Spring的配置类(如使用@Configuration注解的类)上使用@EnableScheduling注解时,Spring会自动配置一个任务调度器(TaskScheduler),负责管理所有带有@Scheduled注解的方法。

@Scheduled:Spring框架中用于定时任务调度的注解。它允许开发者将一个方法标记为定时任务,并配置任务的执行时间间隔或Cron表达式,从而实现在指定时间或按照指定周期自动执行该方法的功能。 除了配置Cron表达式外,还可以通过fixedRate和fixedDelay两种方式设置定时任务,这两种方式可以自行了解。

执行结果

按照上面代码中给定的cron表达式@Scheduled(cron = "0/10 * * * * ? ")每十秒执行一次,那么最近几次的执行结果应当为:

5. 问题:执行时间延迟和单线程执行

 如果定时任务中是执行非常快的任务的,时间非常非常短,按照上述方法实现定时任务,确实不会有什么的延迟性,但真实的业务场景中,业务的执行时间可能远比这里时间长。

例如:主动让线程睡上20秒,让我们再来看看输出结果是如何的吧。

@Slf4j
@Component
@EnableScheduling
public class DemoTask {

    // 每10秒执行一次,cron的表达式暂时不多说明了,可以自行百度学习
    @Scheduled(cron = "0/10 * * * * ? ")
    public void testSchedule() {
        try {
            Thread.sleep(20000); // 休眠20秒,模拟业务场景执行时间
            log.info("当前执行任务的线程号ID===>{}", Thread.currentThread().getId()); // 日志输出
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

执行结果

  • 执行时间延迟:从时间上可以明显看出,不再是每10秒执行一次,执行时间延迟很多,造成任务的。
  • 单线程执行:从始至终都只有一个线程在执行任务,造成任务的堵塞。

5.1 问题原因 

  • 任务阻塞:定时任务设置了每10秒执行一次,但任务实际执行了20秒,那么下一个任务的执行就会因为前一个任务尚未完成而被阻塞。
  • 单线程调度器:当使用@EnableScheduling默认配置创建定时任务时,通常Spring会使用一个单线程的TaskScheduler来执行这些定时任务。在默认配置下,这个单线程的调度器会顺序地执行每一个任务,而不会并行处理。

归根到底,线程阻塞式执行,执行任务线程数量过少 ~

5.2 解决方式

方式一:执行逻辑改为异步

首先我们先配置一个线程池,参数自己设置即可,我这里比较随意。

@Configuration
public class DemoTheadPoolConfig {
    @Bean(name = "TaskExecutor")
    public TaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        //设置核心线程数
        executor.setCorePoolSize(10);
        //设置最大线程数
        executor.setMaxPoolSize(20);
        //缓冲队列200:用来缓冲执行任务的队列
        executor.setQueueCapacity(200);
        //线程活路时间 60 秒
        executor.setKeepAliveSeconds(60);
        //线程池名的前缀:设置好了之后可以方便我们定位处理任务所在的线程池
        executor.setThreadNamePrefix("demo-thread-");
        //设置拒绝策略
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.setWaitForTasksToCompleteOnShutdown(true);
        return executor;
    }
}

然后在定时任务中的将模拟业务的休眠20秒,改造成多线程并发的方式执行。

    @Scheduled(cron = "0/10 * * * * ? ")
    public void testSchedule() {
        CompletableFuture.runAsync(() -> {
            try {
                Thread.sleep(20000); // 休眠20秒,模拟业务场景执行时间
                log.info("当前执行任务的线程号ID===>{}", Thread.currentThread().getId()); // 日志输出
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, taskExecutor);
    }

执行结果

 可以看到虽然业务执行时间仍然是20秒,但是10秒的定时任务没有再出现延迟执行的情况。

方式二:异步定时任务

异步定时任务其实和上面的方式原理是一样的,不过实现稍稍不同罢了,在定时任务的类上再加一个@EnableAsync注解,给方法添加一个@Async即可。

注意:

  • @EnableAsync:开启异步任务
  • @Async:给希望异步执行的方法标注
  • 一般使用@Async都会指定线程池,比如写成这样@Async(value = "TaskExecutor")

@Slf4j
@Component
@EnableAsync
@EnableScheduling
public class DemoTask {

    // 每10秒执行一次,cron的表达式暂时不多说明了,可以自行百度学习
    @Async("TaskExecutor")
    @Scheduled(cron = "0/10 * * * * ? ")
    public void testSchedule() {
        try {
            Thread.sleep(20000); // 休眠20秒,模拟业务场景执行时间
            log.info("当前执行任务的线程号ID===>{}", Thread.currentThread().getId()); // 日志输出
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

执行结果

结果显而易见也是和第一种方式一样的。


至此,我们的SpringBoot实现定时任务项目完美竣工!!!

ps:本教程我所阐述的这种方式,只能说适用于简单的单体项目,实际业务比这要复杂的多,如分布式场景还需考虑定时任务在多个机器下运行的问题,后续作者也会针对该问题出一篇分布式锁的教程,如感兴趣关注点一下吧!!!

有什么问题都可以评论区留言,看见都会回复的!!!

如果你觉得本篇文章对你有所帮助的,多多支持!!!

点赞收藏评论,抱拳了!!!

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

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

相关文章

pytorch学习(五)tensorboard使用

1. 创建环境 首先创建一个环境: conda create -n pytorch conda activate pytorch 然后安装tensorboard pip install tensorboard 安装opencv pip install opencv-python 2. 简单的案例 标量和图像的显示&#xff1a; 2.1标量实现的方法是add_scalar,第一个参数是给显…

Stable Diffusion:质量高画风清新细节丰富的二次元大模型二次元插图

今天和大家分享一个基于Pony模型训练的二次元模型&#xff1a;二次元插图。关于该模型有4个不同的分支版本。 1.5版本&#xff1a;loar模型&#xff0c;推荐底模型niji-动漫二次元4.5。 xl版本&#xff1a;SDXL模型版本 mix版本&#xff1a;光影减弱&#xff0c;减少SDXL版本…

21天学通C++:第十三、十四章节

第十三章&#xff1a;类型转换运算符 类型转换是一种机制&#xff0c;让程序员能够暂时或永久性改变编译器对对象的解释。注意&#xff0c;这并不意味着程序员改变了对象本身&#xff0c;而只是改变了对对象的解释。可改变对象解释方式的运算符称为类型转换运算符。 为何需要…

数据库端口LookUp功能:从数据库中获取并添加数据到XML

本文将为大家介绍如何使用知行之桥EDI系统数据库端口的Lookup功能&#xff0c;从数据库中获取数据&#xff0c;并添加进输入的XML中。 使用场景&#xff1a;期待以输入xml中的值为判断条件从数据库中获取数据&#xff0c;并添加进输入xml中。 例如&#xff1a;接收到包含采购…

Linux 06-01:简易shell编写

考虑一下这个与shell典型的互动&#xff1a;ls、ps 用下图的时间轴来表示事件的发生次序。其中时间从左向右。shell由标识为sh的方块代表&#xff0c;它随着时间的流逝从左向右移动。shell从用户读入字符串"ls"。shell建立一个新的进程&#xff0c;然后在那个进程中运…

Three.js 实战【2】—— 船模型海上场景渲染

停止了好久没有更新three这方面的文章了&#xff0c;从上两年还是vue2&#xff0c;一下子都换到vue3了&#xff0c;下面这些three都是基于vue3来进行开发的哈&#xff0c;先看一下这篇文章实现的效果哈。其中关于模型什么的资源都放在Git上了 初始化场景 安装three就直接通过n…

Java——集合(Queue)

1.Queue 接口的常用功能 除了基本的 Collection 操作外&#xff0c;队列还提供其他的插入、提取和检查操作。每个方法都存在 两种形式&#xff1a;一种抛出异常&#xff08;操作失败时&#xff09;&#xff0c;另一种返回一个特殊值&#xff08; null 或 false &#xff…

RPA鼠标按键使用技巧

RPA鼠标按键使用技巧 Mouse.MouseAuto.Action命令出错&#xff0c;调用的目标发生了异常&#xff0c;Exception in Mouse.Action元素不可用怎么解决 出现问题 1.想要实现的效果鼠标移动到录屏工具的小球上2.点击开始按钮开始录屏现象&#xff0c;鼠标没有移动痕迹&#xff0c…

爬虫案例(读书网)(下)

上篇链接&#xff1a; CSDN-读书网https://mp.csdn.net/mp_blog/creation/editor/139306808 可以看见基本的全部信息&#xff1a;如(author、bookname、link.....) 写下代码如下&#xff1a; import requests from bs4 import BeautifulSoup from lxml import etreeheaders{…

SSD实现

一、模型 此模型主要由基础网络组成&#xff0c;其后是几个多尺度特征块。基本网络用于从输入图像中提取特征&#xff0c;因此它可以使用深度卷积神经网络。 单发多框检测选用了在分类层之前截断的VGG&#xff0c;现在也常用ResNet替代&#xff1b;可以设计基础网络&#xff0c…

【LeetCode】162. 寻找峰值

1. 题目 2. 分析 这道题的难点有二&#xff1a;第一&#xff0c;知道用二分法求解&#xff1b;第二&#xff0c;二分判断的标准是什么&#xff1f;传统的题目的二分标注都是跟某个固定的值做比较&#xff0c;但是此题不然。此题的比较对象是相邻的元素。 不要硬凭自己的脑子…

spring是如何解决循环依赖的,为什么不是两级

1. Spring使用三级缓存来解决循环依赖问题 Spring使用三级缓存来解决循环依赖问题&#xff0c;‌而不是使用两级缓存。‌ 在Spring框架中&#xff0c;‌解决循环依赖的关键在于正确地管理Bean的生命周期和依赖关系。‌循环依赖指的是两个或多个Bean相互依赖&#xff0c;‌如果…

FastApi地理坐标数据存取实践

说明&#xff1a; 应用Pydantic Model 验证/出入 数据&#xff0c; SqlAlchemy Model数据实体&#xff0c;Fastapi提供API机制支持。数据表的坐标字段采用Mysql的GEOMETRY类型目前还没成功使用Pydantic的Coordinate类型&#xff0c;待后续改良 要点&#xff1a; 输出的结果是…

多级表头固定列问题

父级的width&#xff0c;是需要固定的列的width的总和 参考&#xff1a; el-table 多级表头下对应列的固定

Android Studio 不再支持windows 7

Android Studio 一打开就报错&#xff1a; 无法找到入口 无法定位程序输入点 CreateAppContainerProfle 于动态链接库USERENV.dII 上。 截图如下&#xff1a; 经调查&#xff0c;是因为系统版本不兼容。 我目前的电脑环境&#xff1a;windows 7,但是现在的Android Studio要…

leetcode145. 二叉树的后序遍历,递归法+迭代法,全过程图解+步步解析,一点点教会你迭代法后序遍历

leetcode145. 二叉树的后序遍历&#xff0c;递归法迭代法 给你一棵二叉树的根节点 root &#xff0c;返回其节点值的 后序遍历 。 示例 1&#xff1a; 输入&#xff1a;root [1,null,2,3] 输出&#xff1a;[3,2,1] 示例 2&#xff1a; 输入&#xff1a;root [] 输出&#…

Java学习高级四

JDK8开始&#xff0c;接口新增了三种形式的方法 接口的多继承 内部类 成员内部类 静态内部类 局部内部类 匿名内部类 import javax.swing.*; import java.awt.event.ActionEvent;public class Test {public static void main(String[] args) {// 扩展 内部类在开发中的真实使用…

H264解码器实现-帧间预测之MV预测

1.前言 本文章所说的MV预测是指计算当前块MV向量的预测值&#xff0c;该值与码流中传输的MV残差值相加即可得到实际的MV向量。请注意&#xff0c;在某些宏块类型的某种情况下是不需要进行MV预测的&#xff0c;他们的MV可以通过其他方法得出&#xff0c;本文只介绍MV预测过程。…

21.x86游戏实战-实现注入器

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 本次游戏没法给 内容参考于&#xff1a;微尘网络安全 工具下载&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1rEEJnt85npn7N38Ai0_F2Q?pwd6tw3 提…

C++:模板编程入门

什么是模板编程 假设我们需要实现对整形、浮点型、双精度型数据的交换函数&#xff0c;一般情况我们需要重载三个函数&#xff0c;但是使用模板&#xff0c;我们使用一个函数就能解决。 函数在编译时进行重载。 #include <iostream>using namespace std;template <t…