Spring Boot入门(三):使用Scheduled注解实现定时任务
在程序开发的过程中,经常会使用定时任务来实现一些功能,比如:
- 系统依赖于外部系统的非核心数据,可以定时同步
- 系统内部一些非核心数据的统计计算,可以定时计算
- 系统内部的一些接口,需要间隔几分钟或者几秒执行一次
在Spring Boot中,我们可以使用@Scheduled注解来快速的实现这些定时任务。
@Scheduled注解主要支持以下3种方式:
- fixedDelay
- fixedRate
- cron
那么接下来,我们讲解下具体的实现方式以及这3种方式之间的区别。
1.前提
首先,需要在启动类上添加@EnableScheduling注解:
package com.zwwhnly.springbootdemo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@EnableScheduling
public class SpringbootdemoApplication {
/*其他代码*/
public static void main(String[] args) {
SpringApplication.run(SpringbootdemoApplication.class, args);
}
}
然后,新建一个定时任务测试类TestSchedule,该类需要添加注解@Component
package com.zwwhnly.springbootdemo;
import org.springframework.stereotype.Component;
@Component
public class TestSchedule {
private SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}
2.fixedDelay
添加一个测试方法testFixedDelay,该方法添加注解@Scheduled,这里我们先使用最好理解的fixedDelay,为了能看到效果,我们每隔5秒输出下系统的当前时间,如下所示:
// 上次任务执行结束后,间隔5秒再执行下次任务
@Scheduled(fixedDelay = 5000)
public void testFixedDelay()
{
System.out.println("当前时间:" + simpleDateFormat.format(new Date()));
}
启动项目,我们会看到运行结果如下:
当前时间:2019-04-09 10:28:56
当前时间:2019-04-09 10:29:01
当前时间:2019-04-09 10:29:06
当前时间:2019-04-09 10:29:11
当前时间:2019-04-09 10:29:16
从运行结果来看,确实是每隔5秒输出一次。
但是在实际项目中,不可能这么规律,比如方法的执行时间超过了5秒呢(这个应该很常见),那么彼时程序又是如何执行的呢?
我们修改下程序(代码参考文章[肥朝]原理暂且不谈,定时器你当真会用?):
private List<Integer> index = Arrays.asList(8 * 1000, 3 * 1000, 6 * 1000, 2 * 1000, 2 * 1000);
private AtomicInteger atomicInteger = new AtomicInteger(0);
// 上次任务执行结束后,间隔5秒再执行下次任务
@Scheduled(fixedDelay = 5000)
public void testFixedDelay() throws Exception {
int i = atomicInteger.get();
if (i < 5) {
Integer sleepTime = index.get(i);
System.out.println("当前时间:" + simpleDateFormat.format(new Date()));
Thread.sleep(sleepTime);
atomicInteger.getAndIncrement();
}
}
此时的运行结果为:
当前时间:2019-04-18 14:09:26
当前时间:2019-04-18 14:09:39
当前时间:2019-04-18 14:09:47
当前时间:2019-04-18 14:09:58
当前时间:2019-04-18 14:10:05
让我们来分析下运行结果:
第2次输出的时间距离第1次输出的时间间隔为13秒(即方法的执行时间8s+定义的延迟时间5s)。
第3次输出的时间距离第2次输出的时间间隔为8秒(即方法的执行时间3s+定义的延迟时间5s)。
第4次输出的时间距离第3次输出的时间间隔为11秒(即方法的执行时间6s+定义的延迟时间5s)。
第5次输出的时间距离第4次输出的时间间隔为7秒(即方法的执行时间2s+定义的延迟时间5s)。
由此我们可以得出结论:使用fixedDelay,不管方法的执行时间是否超过定义的时间5s,上一个任务执行完成后,都会延迟5s再执行下一个任务。
3.fixedRate
添加测试方法testFixedRate,这次我们使用fixedRate。
// 每5秒执行一次
@Scheduled(fixedRate = 5000)
public void testFixedRate() {
System.out.println("当前时间:" + simpleDateFormat.format(new Date()));
}
启动项目,我们会看到运行结果如下:
从运行结果来看,也是每隔5秒输出一次。
但是在实际项目中,不可能这么规律,比如方法的执行时间超过了5秒呢(这个应该很常见),那么彼时程序又是如何执行的呢?
我们来修改下程序,让方法每次的执行时间不固定:
private List<Integer> index = Arrays.asList(8 * 1000, 3 * 1000, 6 * 1000, 2 * 1000, 2 * 1000);
private AtomicInteger atomicInteger = new AtomicInteger(0);
@Scheduled(fixedRate = 5000)
public void testFixedRate() throws Exception {
int i = atomicInteger.get();
if (i < 5) {
Integer sleepTime = index.get(i);
System.out.println("当前时间:" + simpleDateFormat.format(new Date()));
Thread.sleep(sleepTime);
atomicInteger.getAndIncrement();
}
}
此时的运行结果为:
当前时间:2019-04-18 15:05:46
当前时间:2019-04-18 15:05:54
当前时间:2019-04-18 15:05:57
当前时间:2019-04-18 15:06:03
当前时间:2019-04-18 15:06:06
之前我的理解是上一个任务执行完成后,会立即执行下一个任务,但是细心的网友会发现,最后一次输出的时间明明离上次的时间间隔了3秒,按照立即执行的说法,应该间隔2秒才对。
直到看到这篇文章[肥朝]原理暂且不谈,定时器你当真会用?,我才发现自己之前的理解是错误的,在此也非常感谢这篇文章的作者肥朝。
文章中的例子非常形象的解释了以上结果的原因,这里借鉴下(如有侵权,可联系我删除)。
拿洗澡的这个例子来说。
你可以理解为舍长预算每个同学洗澡的时间是5秒。
但是第一个同学进去洗澡,用了8秒。
第二个同学本来应该在第5秒的时候就进去的,结果第一个同学出来的时候,已经是第8秒了,那么舍长就赶紧催第二个同学进去,把时间进度追回来。
第二个同学知道时间紧,洗了3秒就出来.此时舍长发现,第三个同学,原本应该是在第10秒进去的,结果现在已经到了第11秒(8+3),那么就赶紧催第三个同学进去。
第三个同学沉醉其中,难以自拔的洗了6秒.出来的时候已经是第17秒(8+3+6).舍长掐指一算,发现第四个同学原本应该是第15秒的时候就进去了.结果现在都17秒了,时间不等人,催促第四个同学进去赶紧洗。
第四个同学只洗了2秒就出来了,出来的时候,舍长看了一下时间,是第19秒.有原则的舍长发现,第5个同学原本预算是20秒的时候进去的,结果现在才19秒,不行,那让他在外面玩1秒的手机,等20秒的时候再进去。
4.cron
相比于上面讲的两种方式,cron表达式显得更加灵活,因为它基本满足各种场景的配置需求,比如固定频率执行,固定某个时间点执行等。
首先,我们使用cron表达式实现上述例子中的每隔5秒执行一次:
@Scheduled(cron = "0/5 * * * * ?")
public void testCron() {
System.out.println("当前时间:" + simpleDateFormat.format(new Date()));
}
运行结果为:
当前时间:2019-04-09 11:00:50
当前时间:2019-04-09 11:00:55
当前时间:2019-04-09 11:01:00
当前时间:2019-04-09 11:01:05
当前时间:2019-04-09 11:01:10
修改下代码,让每次方法执行时延迟不同的时间:
private List<Integer> index = Arrays.asList(8 * 1000, 3 * 1000, 6 * 1000, 2 * 1000, 2 * 1000);
private AtomicInteger atomicInteger = new AtomicInteger(0);
@Scheduled(cron = "0/5 * * * * ?")
public void testCron() throws Exception {
int i = atomicInteger.get();
if (i < 5) {
Integer sleepTime = index.get(i);
System.out.println("当前时间:" + simpleDateFormat.format(new Date()));
Thread.sleep(sleepTime);
atomicInteger.getAndIncrement();
}
}
此时的运行结果为:
当前时间:2019-04-18 16:38:10
当前时间:2019-04-18 16:38:20
当前时间:2019-04-18 16:38:25
当前时间:2019-04-18 16:38:35
当前时间:2019-04-18 16:38:40
也许你会发现,有些时间间隔为10s,有些时间间隔为5s,这是为什么呢?
这里再次借鉴下肥朝大佬文章中的解释(如有侵权,可联系我删除):
仍然拿洗澡的这个例子来说。
你可以理解为5s就是一个周期.这就相当于在宿舍洗澡,因为只有一个洗澡位置(单线程),所以每次只能进去一个人,然后舍长在门口,每5s看一下有没有空位,有空位的话叫下一个进去洗澡。
第5秒的时候,舍长看了一下,发现第一个同学还没有出来(因为他洗了8s)。
第二个周期,也就是第10秒的时候再看一下.发现已经有空位了,那么就叫第二个同学进去洗。
第三个周期,也就是15秒的时候,又瞄了一眼,发现有空位了(因为前两位同学用了8+3秒),叫第三个同学进去洗。
第四个周期,也就是20秒的时候,发现没有空位。
第五个周期的时候,也就是25秒的时候.发现有空位了,接着叫下一个进去洗.剩下的不再分析。
如果想要设置成每天的某个时间点执行,比如我的个人博客http://www.zwwhnly.com/就是每晚20:00定时拉取GitHub代码并使用Jekyll编译到Nginx的目录下实现的自动发布。
实现这个配置的cron表达式为:0 0 20 * * ? 。
更多的cron表达式,可以到http://cron.qqe2.com/去自定义,勾选好会自动生成cron表达式,非常方便。
5.源码地址
https://github.com/zwwhnly/springbootdemo.git,欢迎大家下载,有问题可以多多交流。
6.参考链接
springboot使用@Scheduled做定时任务,以及连接池问题
Spring Boot入门(三):使用Scheduled注解实现定时任务的更多相关文章
- Spring Boot入门系列(八)整合定时任务Task,一秒搞定定时任务
前面介绍了Spring Boot 中的整合Redis缓存已经如何实现数据缓存功能.不清楚的朋友可以看看之前的文章:https://www.cnblogs.com/zhangweizhong/categ ...
- Spring boot 入门三:SpringBoot用JdbcTemplates访问Mysql 实现增删改查
建表脚本 -- create table `account`DROP TABLE `account` IF EXISTSCREATE TABLE `account` ( `id` int(11) NO ...
- Spring Boot 入门之持久层篇(三)
原文地址:Spring Boot 入门之持久层篇(三) 博客地址:http://www.extlight.com 一.前言 上一篇<Spring Boot 入门之 Web 篇(二)>介绍了 ...
- Spring Boot 使用 @Scheduled 注解创建定时任务
在项目开发中我们经常需要一些定时任务来处理一些特殊的任务,比如定时检查订单的状态.定时同步数据等等. 在 Spring Boot 中使用 @Scheduled 注解创建定时任务非常简单,只需要两步操作 ...
- spring boot 入门操作(三)
spring boot入门操作 devtools热部署 pom dependencies里添加依赖 <dependency> <groupId>org.springframew ...
- Spring Boot入门(四):开发Web Api接口常用注解总结
本系列博客记录自己学习Spring Boot的历程,如帮助到你,不胜荣幸,如有错误,欢迎指正! 在程序员的日常工作中,Web开发应该是占比很重的一部分,至少我工作以来,开发的系统基本都是Web端访问的 ...
- Spring Boot入门(六):使用MyBatis访问MySql数据库(注解方式)
本系列博客记录自己学习Spring Boot的历程,如帮助到你,不胜荣幸,如有错误,欢迎指正! 本篇博客我们讲解下在Spring Boot中使用MyBatis访问MySql数据库的简单用法. 1.前期 ...
- Spring Boot入门教程2-1、使用Spring Boot+MyBatis访问数据库(CURD)注解版
一.前言 什么是MyBatis?MyBatis是目前Java平台最为流行的ORM框架https://baike.baidu.com/item/MyBatis/2824918 本篇开发环境1.操作系统: ...
- Spring boot入门(三):SpringBoot集成结合AdminLTE(Freemarker),利用generate自动生成代码,利用DataTable和PageHelper进行分页显示
关于SpringBoot和PageHelper,前篇博客已经介绍过Spring boot入门(二):Spring boot集成MySql,Mybatis和PageHelper插件,前篇博客大致讲述了S ...
随机推荐
- 【转】IE浏览器快捷键大全
一般快捷键F11打开/关闭全屏模式 TAB循环的选择地址栏,刷新键和当前标签页 CTRL+F在当前标签页查询字或短语 CTRL+N为当前标签页打开一个新窗口 CTRL+P打印当前标签页 CTRL+A选 ...
- Centos制作本地yum源
本地YUM源制作 1. YUM相关概念 1.1. 什么是YUM YUM(全称为 Yellow dog Updater, Modified)是一个在Fedora和RedHat以及CentOS中的Shel ...
- 【转】JavaScript 错误处理与调试——“错误处理”的注意要点
try-catch语句 该语句最适合处理那些我们无法控制的错误,在明明白白地知道自己的代码会发生错误时,再使用该语句就不太合适了. ECMA-262第3版引入了try-catch语句,基本的语法如下所 ...
- linux C中调用shell命令和运行shell脚本
1.system(执行shell 命令) 相关函数 fork,execve,waitpid,popen表头文件 #include<stdlib.h>定义函数 int system(cons ...
- Maven通俗讲解
也许是本人不才,初识Maven时,被各种不明所以的教程搞得一头雾水,而在后来的使用中,我发现Maven大部分功能没有想象的那么困难. 本片文章面向Maven初学者,希望能让其以最快的速度了解Maven ...
- Scrapy 和 scrapy-redis的区别
Scrapy 和 scrapy-redis的区别 Scrapy 是一个通用的爬虫框架,但是不支持分布式,Scrapy-redis是为了更方便地实现Scrapy分布式爬取,而提供了一些以redis为基础 ...
- Servlet到底是单例还是多例你了解吗?
为一个Java Web开发者,你一定了解和学习过Servlet.或许还曾在面试中被问到过Servelt是单例还是多例这个问题. 遇到这个问题,你是否曾深入了解过,还是百度或者Google了一下,得到答 ...
- OAuth 2 Developers Guide
Introduction This is the user guide for the support for OAuth 2.0. For OAuth 1.0, everything is diff ...
- 新浪微博注册(elenium Python 自动化)
from selenium import webdriverfrom selenium.webdriver.common.keys import Keysfrom time import sleep ...
- Java 读书笔记 (二) 对象和类
Java 作为一种面向对象语言,支持以下基本概念: 多态 继承 封闭 抽象 类 对象 实例 方法 重载 对象: 是类的一个实例,有状态和行为.以人为例,黄种人.白种人.黑种人为类,每一个具体的人为类的 ...