在程序开发的过程中,经常会使用定时任务来实现一些功能,比如:

  • 系统依赖于外部系统的非核心数据,可以定时同步
  • 系统内部一些非核心数据的统计计算,可以定时计算
  • 系统内部的一些接口,需要间隔几分钟或者几秒执行一次

在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做定时任务

springboot使用@Scheduled做定时任务,以及连接池问题

[肥朝]原理暂且不谈,定时器你当真会用?

Spring Boot入门(三):使用Scheduled注解实现定时任务的更多相关文章

  1. Spring Boot入门系列(八)整合定时任务Task,一秒搞定定时任务

    前面介绍了Spring Boot 中的整合Redis缓存已经如何实现数据缓存功能.不清楚的朋友可以看看之前的文章:https://www.cnblogs.com/zhangweizhong/categ ...

  2. Spring boot 入门三:SpringBoot用JdbcTemplates访问Mysql 实现增删改查

    建表脚本 -- create table `account`DROP TABLE `account` IF EXISTSCREATE TABLE `account` ( `id` int(11) NO ...

  3. Spring Boot 入门之持久层篇(三)

    原文地址:Spring Boot 入门之持久层篇(三) 博客地址:http://www.extlight.com 一.前言 上一篇<Spring Boot 入门之 Web 篇(二)>介绍了 ...

  4. Spring Boot 使用 @Scheduled 注解创建定时任务

    在项目开发中我们经常需要一些定时任务来处理一些特殊的任务,比如定时检查订单的状态.定时同步数据等等. 在 Spring Boot 中使用 @Scheduled 注解创建定时任务非常简单,只需要两步操作 ...

  5. spring boot 入门操作(三)

    spring boot入门操作 devtools热部署 pom dependencies里添加依赖 <dependency> <groupId>org.springframew ...

  6. Spring Boot入门(四):开发Web Api接口常用注解总结

    本系列博客记录自己学习Spring Boot的历程,如帮助到你,不胜荣幸,如有错误,欢迎指正! 在程序员的日常工作中,Web开发应该是占比很重的一部分,至少我工作以来,开发的系统基本都是Web端访问的 ...

  7. Spring Boot入门(六):使用MyBatis访问MySql数据库(注解方式)

    本系列博客记录自己学习Spring Boot的历程,如帮助到你,不胜荣幸,如有错误,欢迎指正! 本篇博客我们讲解下在Spring Boot中使用MyBatis访问MySql数据库的简单用法. 1.前期 ...

  8. Spring Boot入门教程2-1、使用Spring Boot+MyBatis访问数据库(CURD)注解版

    一.前言 什么是MyBatis?MyBatis是目前Java平台最为流行的ORM框架https://baike.baidu.com/item/MyBatis/2824918 本篇开发环境1.操作系统: ...

  9. Spring boot入门(三):SpringBoot集成结合AdminLTE(Freemarker),利用generate自动生成代码,利用DataTable和PageHelper进行分页显示

    关于SpringBoot和PageHelper,前篇博客已经介绍过Spring boot入门(二):Spring boot集成MySql,Mybatis和PageHelper插件,前篇博客大致讲述了S ...

随机推荐

  1. c语言中realloc()函数解析

    一.基本特性 1. realloc()函数可以重用或扩展以前用malloc().calloc()及realloc()函数自身分配的内存. 2. realloc()函数需两个参数:一个是包含地址的指针( ...

  2. BootStrap 常用控件总结

    下拉选择Select2:http://ivaynberg.github.io/select2/index.html 文件上传bootstrap-fileinput:https://github.com ...

  3. SDKManager连不上墙外的网,列表刷新不出来怎么办?

    现在我们都无法正常访问外国的网站了,尤其是Google,这个也没了,问题出不去,好多东西都解决的不是很好,包括SDKManager这个都无法刷新列表,当然在这里,我没有介绍你使用VPN的使用,这样做就 ...

  4. Java 学习路线之四个阶段

    写这篇总结,主要是记录下自己的学习经历,算是自己对知识的一个回顾.也给想要学习 Java 的提供一些参考,对于一些想要学习Java,又不知道从哪里下手,以及现在有哪些主流的 Java 技术.想必大家学 ...

  5. How nginx "location if" works

    Nginx's if directive does have some weirdness in practice. And people may misuse it when they do not ...

  6. BOM(浏览器对象模型)

    .t1 { background-color: #ff8080; width: 1100px; height: 40px } 一.BOM(浏览器对象模型) 1.screen对象. console.lo ...

  7. Eclipse报错Resource '/.org.eclipse.jdt.core.external.folders/.link5' already exists.

    Eclipse查看源码出现source not found,重新Build Path选择jdk的jar包时,出现Resource '/.org.eclipse.jdt.core.external.fo ...

  8. ubuntu下dpkg、phantomjs安装包下载地址

    dpkg下载地址 http://download.chinaunix.net/download/0003000/2377.shtml phantomjs下载地址 https://bitbucket.o ...

  9. 在centos上面安装phantomjs

    在opt目录下创建phantomjs文件夹 mkdir -p /opt/phantomjs 把phantomjs解压出来的的文件放到/opt/phantomjs下面 建立软链接 ln -s /opt/ ...

  10. gitlab pipelines job执行时日志较大报错

    问题描述 gitlab pipelines job执行时日志较大报错 Job's log exceeded limit of 4194304 bytes. 解决方案 出现该问题主要是因为gitlab ...