一、背景介绍

在实际的业务开发过程中,我们经常会需要定时任务来帮助我们完成一些工作,例如每天早上 6 点生成销售报表、每晚 23 点清理脏数据等等。

如果你当前使用的是 SpringBoot 来开发项目,那么完成这些任务会非常容易!

SpringBoot 默认已经帮我们完成了相关定时任务组件的配置,我们只需要添加相应的注解@Scheduled就可以实现任务调度!

二、方案实践

2.1、pom 包配置

pom包里面只需要引入Spring Boot Starter包即可!

<dependencies>
<!--spring boot核心-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!--spring boot 测试-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

2.2、启动类启用定时调度

在启动类上面加上@EnableScheduling即可开启定时

@SpringBootApplication
@EnableScheduling
public class ScheduleApplication { public static void main(String[] args) {
SpringApplication.run(ScheduleApplication.class, args);
}
}

2.3、创建定时任务

Spring Scheduler支持四种形式的任务调度!

  • fixedRate:固定速率执行,例如每5秒执行一次
  • fixedDelay:固定延迟执行,例如距离上一次调用成功后2秒执行
  • initialDelay:初始延迟任务,例如任务开启过5秒后再执行,之后以固定频率或者间隔执行
  • cron:使用 Cron 表达式执行定时任务
2.3.1、固定速率执行

你可以通过使用fixedRate参数以固定时间间隔来执行任务,示例如下:

@Component
public class SchedulerTask { private static final Logger log = LoggerFactory.getLogger(SchedulerTask.class);
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); /**
* fixedRate:固定速率执行。每5秒执行一次。
*/
@Scheduled(fixedRate = 5000)
public void runWithFixedRate() {
log.info("Fixed Rate Task,Current Thread : {},The time is now : {}", Thread.currentThread().getName(), dateFormat.format(new Date()));
}
}

运行ScheduleApplication主程序,即可看到控制台输出效果:

Fixed Rate Task,Current Thread : scheduled-thread-1,The time is now : 2020-12-15 11:46:00
Fixed Rate Task,Current Thread : scheduled-thread-1,The time is now : 2020-12-15 11:46:10
...
2.3.2、固定延迟执行

你可以通过使用fixedDelay参数来设置上一次任务调用完成与下一次任务调用开始之间的延迟时间,示例如下:

@Component
public class SchedulerTask { private static final Logger log = LoggerFactory.getLogger(SchedulerTask.class);
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); /**
* fixedDelay:固定延迟执行。距离上一次调用成功后2秒后再执行。
*/
@Scheduled(fixedDelay = 2000)
public void runWithFixedDelay() {
log.info("Fixed Delay Task,Current Thread : {},The time is now : {}", Thread.currentThread().getName(), dateFormat.format(new Date()));
}
}

控制台输出效果:

Fixed Delay Task,Current Thread : scheduled-thread-1,The time is now : 2020-12-15 11:46:00
Fixed Delay Task,Current Thread : scheduled-thread-1,The time is now : 2020-12-15 11:46:02
...
2.3.3、初始延迟任务

你可以通过使用initialDelay参数与fixedRate或者fixedDelay搭配使用来实现初始延迟任务调度。

@Component
public class SchedulerTask { private static final Logger log = LoggerFactory.getLogger(SchedulerTask.class);
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); /**
* initialDelay:初始延迟。任务的第一次执行将延迟5秒,然后将以5秒的固定间隔执行。
*/
@Scheduled(initialDelay = 5000, fixedRate = 5000)
public void reportCurrentTimeWithInitialDelay() {
log.info("Fixed Rate Task with Initial Delay,Current Thread : {},The time is now : {}", Thread.currentThread().getName(), dateFormat.format(new Date()));
}
}

控制台输出效果:

Fixed Rate Task with Initial Delay,Current Thread : scheduled-thread-1,The time is now : 2020-12-15 11:46:05
Fixed Rate Task with Initial Delay,Current Thread : scheduled-thread-1,The time is now : 2020-12-15 11:46:10
...
2.3.4、使用 Cron 表达式

Spring Scheduler同样支持Cron表达式,如果以上简单参数都不能满足现有的需求,可以使用 cron 表达式来定时执行任务。

关于cron表达式的具体用法,可以点击参考这里: https://cron.qqe2.com/

@Component
public class SchedulerTask { private static final Logger log = LoggerFactory.getLogger(SchedulerTask.class);
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); /**
* cron:使用Cron表达式。每6秒中执行一次
*/
@Scheduled(cron = "*/6 * * * * ?")
public void reportCurrentTimeWithCronExpression() {
log.info("Cron Expression,Current Thread : {},The time is now : {}", Thread.currentThread().getName(), dateFormat.format(new Date()));
}
}

控制台输出效果:

Cron Expression,Current Thread : scheduled-thread-1,The time is now : 2020-12-15 11:46:06
Cron Expression,Current Thread : scheduled-thread-1,The time is now : 2020-12-15 11:46:12
...

2.4、异步执行定时任务

在介绍异步执行定时任务之前,我们先看一个例子!

在下面的示例中,我们创建了一个每隔2秒执行一次的定时任务,在任务里面大概需要花费 3 秒钟,猜猜执行结果如何?

@Component
public class AsyncScheduledTask { private static final Logger log = LoggerFactory.getLogger(AsyncScheduledTask.class);
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); @Scheduled(fixedRate = 2000)
public void runWithFixedDelay() {
try {
TimeUnit.SECONDS.sleep(3);
log.info("Fixed Delay Task, Current Thread : {} : The time is now {}", Thread.currentThread().getName(), dateFormat.format(new Date()));
} catch (InterruptedException e) {
log.error("错误信息",e);
}
}
}

控制台输入结果:

Fixed Delay Task, Current Thread : scheduling-1 : The time is now 2020-12-15 17:55:26
Fixed Delay Task, Current Thread : scheduling-1 : The time is now 2020-12-15 17:55:31
Fixed Delay Task, Current Thread : scheduling-1 : The time is now 2020-12-15 17:55:36
Fixed Delay Task, Current Thread : scheduling-1 : The time is now 2020-12-15 17:55:41
...

很清晰的看到,任务调度频率变成了每隔5秒调度一次!

这是为啥呢?

Current Thread : scheduling-1输出结果可以很看到,任务执行都是同一个线程!默认的情况下,@Scheduled任务都在 Spring 创建的大小为 1 的默认线程池中执行!

更直观的结果是,任务都是串行执行!

下面,我们将其改成异步线程来执行,看看效果如何?

@Component
@EnableAsync
public class AsyncScheduledTask { private static final Logger log = LoggerFactory.getLogger(AsyncScheduledTask.class);
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); @Async
@Scheduled(fixedDelay = 2000)
public void runWithFixedDelay() {
try {
TimeUnit.SECONDS.sleep(3);
log.info("Fixed Delay Task, Current Thread : {} : The time is now {}", Thread.currentThread().getName(), dateFormat.format(new Date()));
} catch (InterruptedException e) {
log.error("错误信息",e);
}
}
}

控制台输出结果:

Fixed Delay Task, Current Thread : SimpleAsyncTaskExecutor-1 : The time is now 2020-12-15 18:55:26
Fixed Delay Task, Current Thread : SimpleAsyncTaskExecutor-2 : The time is now 2020-12-15 18:55:28
Fixed Delay Task, Current Thread : SimpleAsyncTaskExecutor-3 : The time is now 2020-12-15 18:55:30
...

任务的执行频率不受方法内的时间影响,以并行方式执行!

2.5、自定义任务线程池

虽然默认的情况下,@Scheduled任务都在 Spring 创建的大小为 1 的默认线程池中执行,但是我们也可以自定义线程池,只需要实现SchedulingConfigurer类即可!

自定义线程池示例如下:

@Configuration
public class SchedulerConfig implements SchedulingConfigurer { @Override
public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
//线程池大小为10
threadPoolTaskScheduler.setPoolSize(10);
//设置线程名称前缀
threadPoolTaskScheduler.setThreadNamePrefix("scheduled-thread-");
//设置线程池关闭的时候等待所有任务都完成再继续销毁其他的Bean
threadPoolTaskScheduler.setWaitForTasksToCompleteOnShutdown(true);
//设置线程池中任务的等待时间,如果超过这个时候还没有销毁就强制销毁,以确保应用最后能够被关闭,而不是阻塞住
threadPoolTaskScheduler.setAwaitTerminationSeconds(60);
//这里采用了CallerRunsPolicy策略,当线程池没有处理能力的时候,该策略会直接在 execute 方法的调用线程中运行被拒绝的任务;如果执行程序已关闭,则会丢弃该任务
threadPoolTaskScheduler.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
threadPoolTaskScheduler.initialize(); scheduledTaskRegistrar.setTaskScheduler(threadPoolTaskScheduler);
}
}

我们启动服务,看看cron任务示例调度效果:

Cron Expression,Current Thread : scheduled-thread-1,The time is now : 2020-12-15 20:46:00
Cron Expression,Current Thread : scheduled-thread-2,The time is now : 2020-12-15 20:46:06
Cron Expression,Current Thread : scheduled-thread-3,The time is now : 2020-12-15 20:46:12
Cron Expression,Current Thread : scheduled-thread-4,The time is now : 2020-12-15 20:46:18
....

当前线程名称已经被改成自定义scheduled-thread的前缀!

三、小结

本文主要围绕Spring scheduled应用实践进行分享,如果是单体应用,使用SpringBoot内置的@scheduled注解可以解决大部分业务需求,上手非常容易!

项目源代码地址:spring-boot-example-scheduled

四、参考

1、https://springboot.io/t/topic/2758

3分钟带你搞定Spring Boot中Schedule的更多相关文章

  1. 3步轻松搞定Spring Boot缓存

    作者:谭朝红 前言 本次内容主要介绍基于Ehcache 3.0来快速实现Spring Boot应用程序的数据缓存功能.在Spring Boot应用程序中,我们可以通过Spring Caching来快速 ...

  2. 【项目实践】一文带你搞定Spring Security + JWT

    以项目驱动学习,以实践检验真知 前言 关于认证和授权,R之前已经写了两篇文章: [项目实践]在用安全框架前,我想先让你手撸一个登陆认证 [项目实践]一文带你搞定页面权限.按钮权限以及数据权限 在这两篇 ...

  3. 一文搞定Spring Boot + Vue 项目在Linux Mysql环境的部署(强烈建议收藏)

    本文介绍Spring Boot.Vue .Vue Element编写的项目,在Linux下的部署,系统采用Mysql数据库.按照本文进行项目部署,不迷路. 1. 前言 典型的软件开发,经过" ...

  4. 不用找了,300 分钟帮你搞定 Spring Cloud!

    最近几年,微服务架构一跃成为 IT 领域炙手可热的话题,大量一线互联网公司因为庞大的业务体量和业务需求,纷纷投入了微服务架构的建设中,像阿里巴巴.百度.美团等大厂,很早就已经开始了微服务的实践和应用. ...

  5. 一行配置搞定 Spring Boot项目的 log4j2 核弹漏洞!

    相信昨天,很多小伙伴都因为Log4j2的史诗级漏洞忙翻了吧? 看到群里还有小伙伴说公司里还特别建了800+人的群在处理... 好在很快就有了缓解措施和解决方案.同时,log4j2官方也是速度影响发布了 ...

  6. 一道题带你搞定Python函数中形参和实参问题

    昨天在Python学习群里有位路人甲问了个Python函数中关于形参和实参一个很基础的问题,虽然很基础,但是对于很多小白来说不一定简单,反而会被搞得稀里糊涂.人生苦短,我用Python. 为了解答大家 ...

  7. 是时候搞清楚 Spring Boot 的配置文件 application.properties 了!

    在 Spring Boot 中,配置文件有两种不同的格式,一个是 properties ,另一个是 yaml . 虽然 properties 文件比较常见,但是相对于 properties 而言,ya ...

  8. 第二篇:彻底搞清楚 Spring Boot 的配置文件 application.properties

    前言 在Spring Boot中,配置文件有两种不同的格式,一个是properties,另一个是yaml. 虽然properties文件比较常见,但是相对于properties而言,yaml更加简洁明 ...

  9. Spring Boot 中的同一个 Bug,竟然把我坑了两次!

    真是郁闷,不过这事又一次提醒我解决问题还是要根治,不能囫囵吞枣,否则相同的问题可能会以不同的形式出现,每次都得花时间去搞.刨根问底,一步到位,再遇到类似问题就可以分分钟解决了. 如果大家没看过松哥之前 ...

  10. Spring Boot2 系列教程(五)Spring Boot中的 yaml 配置

    搞 Spring Boot 的小伙伴都知道,Spring Boot 中的配置文件有两种格式,properties 或者 yaml,一般情况下,两者可以随意使用,选择自己顺手的就行了,那么这两者完全一样 ...

随机推荐

  1. Linux使用宝塔面板发布.NetCore3.1

    1,安装宝塔面板 yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0 ...

  2. 使用interface化解一场因操作系统不同导致的编译问题

    场景描述 起因: 因项目需求,需要编写一个agent, 需支持Linux和Windows操作系统. Agent里面有一个功能需要获取到服务器上所有已经被占用的端口. 实现方式:针对不同的操作系统,实现 ...

  3. vue3 elementui plus Select 选择器不选择下拉框里面的数据得到的value值

    我们先来看 我点击弹框出现什么都不做 直接点击确定 看传参 tableId,timeColumnId,userColumnId直接是名称而没有获取value值 看下下拉框里面的数据 而我想直接点击获取 ...

  4. kettle从入门到精通 第三十四课 kettle 错误处理

    1.我们在平常写应用程序的时候,需要主动捕获异常或者错误,不然程序有可能异常退出.同样kettle 也支持异常或者错误处理,下图展示的是在批量插入数据的时候捕获异常,如唯一健冲突,死锁等,并将错误信息 ...

  5. kettle从入门到精通 第十课 kettle switch/case、过滤记录、数值范围

    1.java代码里面有if else .switch-case等流程控制,kettle也有相应控件.下图便用到switch/case.过滤记录.数值范围控件. 2. switch/case步骤 1)步 ...

  6. Easysearch 压缩功能的显著提升:从 8.7GB 到 1.4GB

    引言 在海量数据的存储和处理中,索引膨胀率是一个不可忽视的关键指标.它直接影响了存储成本和查询性能.近期,Easysearch 在这方面取得了显著的进展,其压缩功能的效果远超过了之前的版本.本文将详细 ...

  7. 《Android开发卷——开卷》

    打算在自己在工作中遇到的问题,技术难点都记录下来,让其他人可以借鉴或者指点,这样既可以成长自己也可以成长别人.因为自己已经在工作了,所以遇到的问题非常具有代表性,所以不能简单简单的像网上一些小学生一样 ...

  8. 什么是 SpringMvc?

    SpringMvc 是 spring 的一个模块,基于 MVC 的一个框架,无需中间整合层来整合.

  9. Centos7部署FytSoa项目至Docker——第二步:安装Mysql、Redis

    FytSoa项目地址:https://gitee.com/feiyit/FytSoaCms 部署完成地址:http://82.156.127.60:8001/ 先到腾讯云申请一年的云服务器,我买的是一 ...

  10. Mysql int类型字段插入表达式,值为0或1

    CREATE TABLE `t_user` ( `uId` INT(11) DEFAULT NULL, `uName` VARCHAR(20) DEFAULT NULL, `uPwd` VARCHAR ...