Spring Task 超详解版

目录

一、定时任务的理解

二、入门案例

三、Cron表达式

四、Cron实战案例

五、多线程案例


一、定时任务的理解

定时任务即系统在特定时间执行一段代码,它的场景应用非常广泛:

  • 购买游戏的月卡会员后,系统每天给会员发放游戏资源。
  • 管理系统定时生成报表。
  • 定时清理系统垃圾。

定时任务的实现主要有以下几种方式:

  1. Java自带的java.util.Timer类,这个类允许调度一个java.util.TimerTask任务。使用这种方式可以让程序按照某一个频度执行,但不能在指定时间运行。一般用的较少。
  2. Quartz。这是一个功能比较强大的的调度器,可以让程序在指定时间执行,也可以按照某一个频度执行,配置起来稍显复杂。
  3. Spring3.0以后自带Spring Task,可以将它看成一个轻量级的Quartz,使用起来比 Quartz简单许多,在课程中我们使用Spring Task实现定时任务

二、入门案例

创建SpringBoot项目,在启动类开启定时任务。

也就是在启动类上方添加@EnableScheduling注解即可开启定时任务,代码如下:

package com.example.springboottaskdemo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableScheduling
public class SpringboottaskdemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringboottaskdemoApplication.class, args);
    }

}

编写定时任务类 

@Component
public class MyTask {
  // 定时任务方法,每秒执行一次
  @Scheduled(cron="* * * * * *")
  public void task1() {
    SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
    System.out.println(sdf.format(new Date()));
 }
}

启动项目,定时任务方法按照配置定时执行。 

Spring Task 超详解版_第1张图片

OK,果然如此,每隔一秒输出当前时间

@Scheduled写在方法上方,指定该方法定时执行。常用参数如下:

  1. cron:cron表达式,定义方法执行的时间规则。
  2. fixedDelay:任务立即执行,之后每隔多久执行一次,单位是毫秒,上一次任务结束后计算下次执行的时间。

OK,先来一个案例,代码如下:任务结束后每隔五秒执行一次

// 立即执行,任务结束后五秒执行一次
    @Scheduled(fixedDelay = 5000)
    public void task1() throws InterruptedException {
        SimpleDateFormat sdf = new SimpleDateFormat("YYYY-MM-dd HH:mm:ss");
        System.out.println("Task1: "+sdf.format(new Date()));
    }

效果如下: 

Spring Task 超详解版_第2张图片

OK,果然如此,注意这个是任务结束后每隔五秒,如果方法中间加了一个sleep方法,那么执行时间还要加上sleep里面的值,比如说中间加了一个sleep(1000),那么就会每隔6秒执行一次。

fixedRate:任务立即执行,之后每隔多久执行一次,单位是毫秒,上一次任务开始后计算下次执行的时间。

案例如下,代码如下:

// 立即执行,之后每五秒执行一次
    @Scheduled(fixedRate = 5000)
    public void task2() throws InterruptedException {
        SimpleDateFormat sdf = new SimpleDateFormat("YYYY-MM-dd HH:mm:ss");
        // 没有影响五秒输出一次
        Thread.sleep(1000);
        System.out.println("Task2: "+sdf.format(new Date()));
    }

 OK,看如下执行代码确实是不受到sleep影响的

Spring Task 超详解版_第3张图片

initialDelay:项目启动后不马上执行定时器,根据initialDelay的值延时执行。 为了突出刚刚说的fixedDelay会受到sleep影响,这里配合fixedDelay来结合测试演示一下:

代码如下:

// 项目启动后三秒执行,之后每六秒执行一次
    @Scheduled(fixedDelay = 5000,initialDelay = 3000)
    public void task3() throws InterruptedException {
        SimpleDateFormat sdf = new SimpleDateFormat("YYYY-MM-dd HH:mm:ss");
        // 没有影响五秒输出一次
        Thread.sleep(1000);
        System.out.println("Task3: "+sdf.format(new Date()));
    }

OK,看运行结果也是隔了三秒才出现第一次打印时间,并且打印时间是隔六秒打印一次 

Spring Task 超详解版_第4张图片

三、Cron表达式

Spring Task依靠Cron表达式配置定时规则。Cron表达式是一个字符串,分为6或7个域,每一个域代表一个含义,以空格隔开。有如下两种语法格式:

  1. Seconds Minutes Hours DayofMonth Month DayofWeek Year
  2. Seconds Minutes Hours DayofMonth Month DayofWeek

Seconds(秒):域中可出现 , - * / 四个字符,以及0-59的整数

  • * :表示匹配该域的任意值,在Seconds域使用 * ,表示每秒钟都会触发
  • , :表示列出枚举值。在Seconds域使用 5,20 ,表示在5秒和20秒各触发一次。
  • - :表示范围。在Seconds域使用 5-20 ,表示从5秒到20秒每秒触发一次
  • / :表示起始时间开始触发,然后每隔固定时间触发一次。在Seconds域使用 5/20 , 表示5秒触发一次,25秒,45秒分别触发一次。

Minutes(分):域中可出现 , - * / 四个字符,以及0-59的整数
Hours(时):域中可出现 , - * / 四个字符,以及0-23的整数
DayofMonth(日期):域中可出现 , - * / ? L W C 八个字符,以及1-31的整数

  • C :表示和当前日期相关联。在DayofMonth域使用 5C ,表示在5日后的那一天触发,且每月的那天都会触发。比如当前是10号,那么每月15号都会触发。
  • L :表示最后,在DayofMonth域使用 L ,表示每个月的最后一天触发。
  • W :表示工作日,在DayofMonth域用 15W ,表示最接近这个月第15天的工作日触发,如果15号是周六,则在14号即周五触发;如果15号是周日,则在16号即周一触发;如果15号是周二则在当天触发。

注:

  1. 该用法只会在当前月计算,不会到下月触发。比如在DayofMonth域用 31W ,31号是周日,那么会在29号触发而不是下月1号。
  2. 在DayofMonth域用 LW ,表示这个月的最后一个工作日触发。

Month(月份):域中可出现 , - * / 四个字符,以及1-12的整数或JAN-DEC的单词缩写
DayofWeek(星期):可出现 , - * / ? L # C 八个字符,以及1-7的整数或SUN-SAT 单词缩写,1代表星期天,7代表星期六

  • C :在DayofWeek域使用 2C ,表示在2日后的那一天触发,且每周的那天都会触发。比如当前是周一,那么每周三都会触发。
  • L :在DayofWeek域使用 L ,表示在一周的最后一天即星期六触发。在DayofWeek域使用 5L ,表示在一个月的最后一个星期四触发。
  • # :用来指定具体的周数, # 前面代表星期几, # 后面代表一个月的第几周,比如 5#3 表示一个月第三周的星期四。
  • ? :在无法确定是具体哪一天时使用,用于DayofMonth和DayofWeek域。例如在每月的20日零点触发1次,此时无法确定20日是星期几,写法如下: 0 0 0 20 * ? ;或者在每月的最后一个周日触发,此时无法确定该日期是几号,写法如下: 0 0 0 ? * 1L

Year(年份):域中可出现 , - * / 四个字符,以及1970~2099的整数。该域可以省略,表示每年都触发。

四、Cron实战案例

下面有常用的案例,大家可以参考一下

Cron实战案例
含义 表达式
每隔5分钟触发一次 0 0/5 * * * *
每小时触发一次 0 0 * * * *
每天的7点30分触发 0 30 7 * * *
周一到周五的早上6点30分触发 0 30 6 ? * 2-6
每月最后一天早上10点触发 0 0 10 L * ?
每月最后一个工作日的18点30分触发 0 30 18 LW * ?
2030年8月每个星期六和星期日早上10点触发 0 0 10 ? 8 1,7 2030
每天10点、12点、14点触发 0 0 10,12,14 * * *
朝九晚五工作时间内每半小时触发一次 0 0 0/30 9-17 ? * 2-6
每周三中午12点触发一次 0 0 12 ? * 4
每天12点触发一次 0 0 12 * * *
每天14点到14:59每分钟触发一次 0 * 14 * * *
每天14点到14:59每5分钟触发一次 0 0/5 14 * * *
每天14点到14:05每分钟触发一次 0 0-5 14 * * *
每月15日上午10:15触发 0 15 10 15 * ?
每月最后一天的上午10:15触发 0 15 10 L * ?
每月的第三个星期五上午10:15触发 0 15 10 ? * 6#3

好啦,通过这些大家应该就可以领悟了

五、多线程案例

Spring Task定时器默认是单线程的,如果项目中使用多个定时器,使用一个线程会造成效率低下。

比如说我们设置了两个定时任务,那么因为Spring Task是单线程,如果在第一个定时任务加了一个sleep方法,那么会等第一个方法响应后在执行第二个任务,就很浪费cpu运行时间。代码如下:

    @Scheduled(cron = "* * * * * *")
    public void task1() throws InterruptedException {
        System.out.println(Thread.currentThread().getId()+"线程执行任务1 - "+new SimpleDateFormat("HH:mm:ss").format(new Date());
        Thread.sleep(5000);
    }

    @Scheduled(cron = "* * * * * *")
    public void task2() throws InterruptedException {
        System.out.println(Thread.currentThread().getId()+"线程执行任务2 - "+new SimpleDateFormat("HH:mm:ss").format(new Date());
    }

执行效果如下:可以看到是先执行了任务2,但是他们都要隔五秒才能运行一次,因为通过线程号可以知道这是同一个线程。

Spring Task 超详解版_第5张图片

因此任务1较浪费时间,会阻塞任务2的运行。此时我们可以给SpringTask配置线程池。代码如下:

package com.example.springboottaskdemo;

import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;

import java.util.concurrent.Executors;

@Configuration
public class SchedulingConfig implements SchedulingConfigurer {
    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        // 创建线程池,设置五个线程
        taskRegistrar.setScheduler(Executors.newScheduledThreadPool(4));
    }
}

这样就不会出现阻塞问题了,因为两个任务不是同一个线程,接下来我们再次运行看看:

执行效果如上,确实不会影响到任务2的运行,但是如果定时任务过多,超过了配置的线程池的线程数量还是会运行错乱。

Ok,SpringBoot到这里就完结撒花了。

你可能感兴趣的:(SpringBoot,spring,mysql,java,spring,boot,maven)