基本概念
Spring Task 主要是用来实现定时任务的,定时任务的业务场景非常常见,比如:
① 数据定时备份
② 视频网站定时发布视频
③ 系统定时生成数据报表
我们可以直接使用 Spring 提供的 @Scheduled
注解即可定义定时任务,默认情况下,@Scheduled
的任务都在 Spring 创建的大小为 1 的默认线程池中执行(ScheduledThreadPoolExecutor
)。该注解常用的几个参数如下:
1.cron
使用 Cron 表达式定义任务的执行时间。Cron 表达式是一种用于调度任务的字符串,通常用于定义定时任务的执行时间。它由六或七个字段组成,每个字段代表一个时间单位,依次为秒、分钟、小时、日期、月份、星期几和(可选的)年份。各字段通过空格分隔
在线 Cron 表达式生成器:http://cron.qqe2.com/
每一个字段的有效范围如下:
① 秒(Seconds):0-59
② 分钟(Minutes):0-59
③ 小时(Hours):0-23
④ 日期(Day of Month):1-31
⑤ 月份(Month):1-12 或 JAN-DEC
⑥ 星期几(Day of Week):0-7(0、7 表示星期天,1 表示星期二,以此类推)或 SUN-SAT
⑦ 年份(Year,可选):1970-2099
特殊字符:
① *
:代表所有可能的值。例如,在秒字段为 *
表示每一秒触发
② ,
:用于指定多个值。例如,在分钟字段中 5,10
表示第 5 和第 10 分钟
③ -
:用于指定一个范围的值。例如,在小时字段中 1-5
表示从 1 点到 5 点每小时触发一次
④ /
:表示起始时间开始触发,然后每隔固定时间触发一次。例如,在秒字段中 0/15
表示起始时间 0s 触发一次,之后每隔 15 秒触发一次
⑤ ?
:只能在日期(Day of Month)和星期几(Day of Week)字段中使用,表示不指定值,在需要指定某一个字段而不关心另一个字段时使用。例如,想要在每月的 20 日触发而不管 20 日是星期几,则在 Day of Week 字段中使用 ?
⑥ L
:只能在日期(Day of Month)和星期几(Day of Week)字段中使用,表示最后。例如,在 Day of Week 字段使用 5L
表示在最后一个星期四触发
⑦ W
:只能在日期(Day of Month)字段中使用,表示工作日,系统将在离指定日期的最近的有效工作日触发事件。例如,在 Day of Month 中使用 5W
,如果 5 日是星期六,则在 4 日(星期五)触发。如果 5 日是星期天,则在 6 日(周一)触发;如果 5 日是星期一到星期五中的一天,就在 5 日触发。另外,W
的最近寻找不会跨过月份
⑧ #
:只能在日期(Day of Month)字段中使用,用于指定每月的第几个星期几。例如,2#1
表示某月的第一个星期一
示例:
(1)每天凌晨 1 点执行一次
0 0 1 * * *
(2)每隔5分钟执行一次
0 */5 * * * *
(3)每天下午 2 点到下午 2:05 期间,每 1 分钟执行一次
0 0-5 14 * * *
(4)每周一的早上 7 点执行一次
0 0 7 ? * MON
2.fixedRate
按固定的频率执行任务,从任务开始执行的时间算起,每隔固定的时间间隔重新执行一次,不管前一个任务是否完成
但这里需要注意的是,如果使用默认的方式,不修改线程池参数,即单线程执行,如果前一个任务执行时长大于定时时长,下一个任务的执行会等到上一个任务执行完后立即执行。但是如果增大线程池的线程数量,如果线程数量足够,下一个任务的执行不会被上一个任务所阻塞,而是会在定时结束后就立即执行,即出现任务并行执行的情况
举例:
(1)任务执行时间为 3s,间隔时间为 5s
第一次执行:0s 开始
第二次执行:5s 开始
第三次执行:10s 开始
(2)任务执行时间为 6s,间隔时间为 5s(单线程)
第一次执行:0s 开始
第二次执行:6s 开始
第三次执行:12s 开始
3.fixedDelay
在前一个任务完成后,等待固定的时间间隔再开始执行下一个任务
举例:
任务执行时间为 3s,间隔时间为 5s
第一次执行:0s 开始
第二次执行:3s(任务完成) + 5s(延迟时间) = 8s 开始
第三次执行: 11s(任务完成) + 5s(延迟时间) = 16s 开始
4.initialDelay
在应用启动后,初始延迟一定时间后开始任务,一般会配合 fixedRate
或 fixedDelay
使用,来指定延迟的时间
代码实现
1.在 Springboot 启动类加上 @EnableScheduling
注解,表示启用 Spring 框架中的计划任务调度功能
@SpringBootApplication
@EnableScheduling
public class TimedTaskDemoApplication {
public static void main(String[] args) {
SpringApplication.run(TimedTaskDemoApplication.class, args);
}
}
2.编写任务类,在定时任务方法上面加上 @Scheduled
注解,注意在类的上面要加上 @Component
注解。这里根据四种常用的参数分别编写不同的任务
(1)cron
@Component
public class ScheduleTask {
@Scheduled(cron = "*/1 * * * * *") // 每秒执行一次
public void task1() {
System.out.println(LocalDateTime.now() + "定时任务");
}
}
测试结果:
(2)fixedRate
@Component
public class ScheduleTask {
private int times = 1;
@Scheduled(fixedRate = 5000) // 延迟 5s
public void task2() {
System.out.println(LocalDateTime.now() + "定时任务开始,当前次数:" + times);
try {
Thread.sleep(3000); // 执行 3s
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(LocalDateTime.now() + "定时任务结束,当前次数;" + times);
times++;
}
}
测试结果:
这里的延迟时间大于任务的执行时间,但当任务的执行时间大于了延迟时间,结果又会不相同,测试代码如下:
@Component
public class ScheduleTask {
private int times = 1;
@Scheduled(fixedRate = 5000) // 延时 5s
public void task3() {
System.out.println(LocalDateTime.now() + "定时任务开始,当前次数:" + times);
try {
Thread.sleep(7000); // 执行 7s
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(LocalDateTime.now() + "定时任务结束,当前次数;" + times);
times++;
}
}
可以看到,设定的延时时间为 5s,而实际上延迟的时间是 7s,原因在于默认线程池大小为 1,任务只能串行执行,后面的任务会受到前面任务执行耗时的影响
这里可以选择开启多线程,在类上方加上 @EnableAsync
注解,对应的方法上方加上 @Async
注解即可
@EnableAsync
@Component
public class ScheduleTask {
@Async
@Scheduled(fixedRate = 5000) // 延迟 5s
public void tasks4() {
System.out.println(LocalDateTime.now() + "定时任务开始,当前线程:" + Thread.currentThread());
try {
Thread.sleep(7000); // 执行 7s
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
测试结果:
可以看到此时的延时时间就是 5s
(3)fixedDelay
@Component
public class ScheduleTask {
private int times = 1;
@Scheduled(fixedDelay = 5000) // 延时 5s
public void task5() {
System.out.println(LocalDateTime.now() + "定时任务开始,当前次数:" + times);
try {
Thread.sleep(3000); // 执行 3s
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(LocalDateTime.now() + "定时任务结束,当前次数;" + times);
times++;
}
}
测试结果:
可以看到,下一次任务的开始时间是上一次任务的结束时间 + 延时时间
(4)initialDelay
@Component
public class ScheduleTask {
private int times = 1;
// 初始 1s,延时 5s
@Scheduled(initialDelay = 1000, fixedRate = 5000)
public void task6() {
System.out.println(LocalDateTime.now() + "定时任务开始,当前次数:" + times);
try {
Thread.sleep(3000); // 执行 7s
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(LocalDateTime.now() + "定时任务结束,当前次数;" + times);
times++;
}
}
测试结果:
可以看到,是在应用启动后 1s 开始第一次任务,随后每次延时了 5s