0%

SpringBoot 开发实践(5):定时任务 @Scheduled

前言

在开发中,我们会有定时执行某些任务的需求,例如定时清理过期文件、定时发送邮件等等。SpringBoot 为我们提供了便捷的方式来配置定时任务,只需要打上几个注解即可。那么下面让我们来看看 SpringBoot 中如何开发定时任务。

开启定时任务

想要使用定时任务,需先打开定时任务开关。
在入口类中添加 @EnableScheduling 注解

1
2
3
4
5
6
7
@SpringBootApplication
@EnableScheduling
public class SchedulerTaskApplication {
public static void main(String[] args) {
SpringApplication.run(SchedulerTaskApplication.class, args);
}
}

使用 @Scheduled 配置定时任务

配置定时任务非常简单,只需要在需要定时执行的方法上添加 @Scheduled 注解即可。注意,该类上需要打上组件型注解,例如 @Componet,这样该类才会被注入到 Spring 容器中进行管理,@Scheduled 才会生效。例如:

1
2
3
4
5
6
7
8
9
10
11
12
@Component
public class SchedulerTask1 {
private static final Logger LOG = LoggerFactory.getLogger(SchedulerTask1.class);

/**
* 每秒执行一次
*/
@Scheduled(cron = "* * * * * ?")
public void scheduler1() {
LOG.info("scheduler1 测试: " + System.currentTimeMillis());
}
}

运行程序,结果如下:

1
2
3
4
5
6
7
8
2020-06-19 00:28:06.002  INFO 12744 --- [   scheduling-1] c.i.s.r.scheduler.SchedulerTask          : scheduler1 测试: 1592497686002
2020-06-19 00:28:07.003 INFO 12744 --- [ scheduling-1] c.i.s.r.scheduler.SchedulerTask : scheduler1 测试: 1592497687003
2020-06-19 00:28:08.002 INFO 12744 --- [ scheduling-1] c.i.s.r.scheduler.SchedulerTask : scheduler1 测试: 1592497688002
2020-06-19 00:28:09.002 INFO 12744 --- [ scheduling-1] c.i.s.r.scheduler.SchedulerTask : scheduler1 测试: 1592497689002
2020-06-19 00:28:10.001 INFO 12744 --- [ scheduling-1] c.i.s.r.scheduler.SchedulerTask : scheduler1 测试: 1592497690001
2020-06-19 00:28:11.001 INFO 12744 --- [ scheduling-1] c.i.s.r.scheduler.SchedulerTask : scheduler1 测试: 1592497691001
2020-06-19 00:28:12.005 INFO 12744 --- [ scheduling-1] c.i.s.r.scheduler.SchedulerTask : scheduler1 测试: 1592497692005
2020-06-19 00:28:13.001 INFO 12744 --- [ scheduling-1] c.i.s.r.scheduler.SchedulerTask : scheduler1 测试: 1592497693001

可以看到,日志每秒钟打印一次,这说明该方法每秒钟被执行一次。

@Scheduled 注解参数说明

@Scheduled支持多种定时规则进行配置。

  • cron: cron 表达式是 Linux 中定时任务的配置规则。具体使用方式可以参考 cron。文末也附上了一些配置示例,方便大家参考。
  • zone: 时区。默认为服务器所在时区,接收类型为 java.util.TimeZone
  • fixedDelay / fixedDelayString: 表示在上次执行之后多久后再次执行。它俩的区别为前者类型为 long 类型,后者为 String,单位均为 ms。
  • fixedRate / fixedRateString: 表示在上次执行开始之后多久后再次执行。同样,它俩的区别也仅为参数类型的区别。
  • initialDelay / initialDelayString: 表示第一次任务执行延迟多久,区别同上。

@Scheduled 的多线程使用

@Scheduled 默认是单线程执行的,多个 @Scheduled 任务都用的同一个线程。如果某个任务是个耗时的操作,那么其它定时任务都会因为这一个耗时的任务而堵塞。

例如,两个定时任务 A、B 都是每秒触发一次。如果任务 A 执行一次需要耗时 5 秒,则任务 A、B 都要等任务 A 执行后才能再执行,这就达不到任务 A、B 每秒执行一次的效果了。

那么,如何能让每个定时任务按照配置好的定时规则准时执行呢?这就需要我们将定时任务配置成多线程的方式。

在入口类中添加 @EnableAsync,开启异步执行。在定时任务上添加 @Async 注解,标识该方法异步执行。方法中我们通过 sleep 来模拟耗时操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Component
public class SchedulerTask2 {
private static final Logger LOG = LoggerFactory.getLogger(SchedulerTask2.class);

/**
* 每秒执行一次
*/
@Async
@Scheduled(cron = "* * * * * ?")
public void scheduler1() {
LOG.info("SchedulerTask2 scheduler1 执行: " + System.currentTimeMillis());
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
LOG.info("SchedulerTask2 scheduler1 执行完了: " + System.currentTimeMillis());
}
}

运行程序我们可以看到:

1
2
3
4
5
6
7
8
9
10
11
2020-06-19 01:13:04.011  INFO 13495 --- [         task-1] c.i.s.r.scheduler.SchedulerTask2         : SchedulerTask2 scheduler1 执行: 1592500384011
2020-06-19 01:13:05.002 INFO 13495 --- [ task-2] c.i.s.r.scheduler.SchedulerTask2 : SchedulerTask2 scheduler1 执行: 1592500385002
2020-06-19 01:13:06.002 INFO 13495 --- [ task-3] c.i.s.r.scheduler.SchedulerTask2 : SchedulerTask2 scheduler1 执行: 1592500386001
2020-06-19 01:13:07.005 INFO 13495 --- [ task-4] c.i.s.r.scheduler.SchedulerTask2 : SchedulerTask2 scheduler1 执行: 1592500387004
2020-06-19 01:13:08.003 INFO 13495 --- [ task-5] c.i.s.r.scheduler.SchedulerTask2 : SchedulerTask2 scheduler1 执行: 1592500388003
2020-06-19 01:13:09.003 INFO 13495 --- [ task-6] c.i.s.r.scheduler.SchedulerTask2 : SchedulerTask2 scheduler1 执行: 1592500389003
2020-06-19 01:13:09.013 INFO 13495 --- [ task-1] c.i.s.r.scheduler.SchedulerTask2 : SchedulerTask2 scheduler1 执行完了: 1592500389013
2020-06-19 01:13:10.004 INFO 13495 --- [ task-2] c.i.s.r.scheduler.SchedulerTask2 : SchedulerTask2 scheduler1 执行完了: 1592500390004
2020-06-19 01:13:10.004 INFO 13495 --- [ task-7] c.i.s.r.scheduler.SchedulerTask2 : SchedulerTask2 scheduler1 执行: 1592500390004
2020-06-19 01:13:11.002 INFO 13495 --- [ task-3] c.i.s.r.scheduler.SchedulerTask2 : SchedulerTask2 scheduler1 执行完了: 1592500391002
2020-06-19 01:13:11.003 INFO 13495 --- [ task-8] c.i.s.r.scheduler.SchedulerTask2 : SchedulerTask2 scheduler1 执行: 1592500391003

虽然方法执行需要耗时 5 秒,但是每个任务还是按照每秒钟触发一次准时执行了。通过前面的 task-n 可以看出,每个任务的执行都使用了不同的线程。

@Async 默认线程池个数为 8 个,我们也可以通过配置来满足不同场景的需要。有关 @Async 多线程的使用,我会在后续的章节为大家介绍。

以上就是 SpringBoot 定时任务的使用方法。

附1:Cron 表达式示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
0 0 2 1 * ? * 表示在每月的1日的凌晨2点调度任务
0 15 10 ? * MON-FRI 表示周一到周五每天上午10:15执行作业
0 15 10 ? * 6L 2002-2006 表示2002-2006年的每个月的最后一个星期五上午10:15执行作
0 0 10,14,16 * * ? 每天上午10点,下午2点,4点
0 0/30 9-17 * * ? 朝九晚五工作时间内每半小时
0 0 12 ? * WED 表示每个星期三中午12点
0 0 12 * * ? 每天中午12点触发
0 15 10 ? * * 每天上午10:15触发
0 15 10 * * ? 每天上午10:15触发
0 15 10 * * ? * 每天上午10:15触发
0 15 10 * * ? 2005 2005年的每天上午10:15触发
0 * 14 * * ? 在每天下午2点到下午2:59期间的每1分钟触发
0 0/5 14 * * ? 在每天下午2点到下午2:55期间的每5分钟触发
0 0/5 14,18 * * ? 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发
0 0-5 14 * * ? 在每天下午2点到下午2:05期间的每1分钟触发
0 10,44 14 ? 3 WED 每年三月的星期三的下午2:10和2:44触发
0 15 10 ? * MON-FRI 周一至周五的上午10:15触发
0 15 10 15 * ? 每月15日上午10:15触发
0 15 10 L * ? 每月最后一日的上午10:15触发
0 15 10 ? * 6L 每月的最后一个星期五上午10:15触发
0 15 10 ? * 6L 2002-2005 002年至2005年的每月的最后一个星期五上午10:15触发
0 15 10 ? * 6#3 每月的第三个星期五上午10:15触发

本章代码:GitHub


我是因特马,一个爱分享的斜杠程序员~

欢迎关注我的公众号:一只因特马

  • 本文作者: 因特马
  • 本文链接: https://www.interhorse.cn/a/1174570529/
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!