欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Spring Boot入门(三):使用Scheduled注解实现定时任务

程序员文章站 2022-03-30 21:17:03
在程序开发的过程中,经常会使用定时任务来实现一些功能,比如: 系统依赖于外部系统的非核心数据,可以定时同步 系统内部一些非核心数据的统计计算,可以定时计算 系统内部的一些接口,需要间隔几分钟或者几秒执行一次 在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()));
}

启动项目,我们会看到运行结果如下:

Spring Boot入门(三):使用Scheduled注解实现定时任务

从运行结果来看,也是每隔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秒的时候.发现有空位了,接着叫下一个进去洗.剩下的不再分析。

如果想要设置成每天的某个时间点执行,比如我的个人博客就是每晚20:00定时拉取github代码并使用jekyll编译到nginx的目录下实现的自动发布。

实现这个配置的cron表达式为:0 0 20 * * ? 。

更多的cron表达式,可以到去自定义,勾选好会自动生成cron表达式,非常方便。

5.源码地址

,欢迎大家下载,有问题可以多多交流。

6.参考链接

springboot 之 使用scheduled做定时任务

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