Spring 3.0 之后提供了 @EnableScheduling
注解和 @Scheduled
注解实现定时任务功能。本案例使用 SpringBoot 创建定时任务,主要有三种创建方式:
- 使用
@Scheduled
注解
- 实现 SchedulingConfigurer 接口
- 基于注解设定多线程定时任务
一、@Scheduled
注解
1、在配置类上使用 @EnableScheduling
注解以开启计划任务。该方式默认为单线程,开启多个任务时,任务的执行时机会受上一个任务执行时间的影响。
1 2 3 4 5 6
| @EnableScheduling public class SnippetApplication { public static void main(String[] args) { SpringApplication.run(SnippetApplication.class, args); } }
|
2、使用 @Scheduled
注解声明这是一个定时任务:
1 2 3 4 5 6 7 8
| @Component public class BaseScheduled {
@Scheduled(cron = "0/10 * * * * ?") public void job() { System.out.println(Thread.currentThread().getName() + "-" + LocalDateTime.now()); } }
|
3、@Scheduled
注解有如下属性
- cron,接收一个 cron 表达式
- zone 时区,接收一个 java.util.TimeZone#ID。默认是一个空字符串,取服务器所在地的时区。
- fixedDelay,服务启动后任务立即执行首次,延迟指定时间后再次执行。例如指定值 10s,相当于 cron 表达式
"0/10 * * * * ?"
- fixedDelayString,同 fixedDelay,值为字符串,并支持占位符
- fixedRate,上一次开始执行时间点之后多长时间再执行
- fixedRateString 同 fixedRate,值为字符串,并支持占位符
- initialDelay,第一次延迟多长时间后再执行
- initialDelayString,同 initialDelay,值为字符串,并支持占位符
- timeUnit,以上计时属性的单位,默认毫秒(
TimeUnit.MILLISECONDS
)
4、cron 属性接收的 cron 表达式支持占位符
application.yml
中添加如下定义
1 2
| scheduled: cron: 0/10 * * * * ?
|
上述代码可更改为
1 2 3 4 5 6 7 8
| @Component public class BaseScheduled {
@Scheduled(cron = "${scheduled.cron}") public void job() { System.out.println(Thread.currentThread().getName() + "-" + LocalDateTime.now()); } }
|
查看完整代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| @Scheduled(fixedDelayString = "5", timeUnit = TimeUnit.SECONDS) public void job() throws InterruptedException, ExecutionException { System.out.println("定时任务开始=" + Thread.currentThread().getName() + "-" + LocalDateTime.now());
BiFunction<Integer, Integer, Callable<String>> function = (id, second) -> { return new Callable<String>() { @Override public String call() throws Exception { TimeUnit.SECONDS.sleep(second); return String.format("任务{ %s }已完成,当前时间={ %s }", id, LocalDateTime.now()); } }; };
completionService.submit(function.apply(1, 2)); completionService.submit(function.apply(2, 8)); completionService.submit(function.apply(3, 10));
for (int index = 0; index < 3; index++) { System.out.println(completionService.take().get()); } }
|
执行几个周期,输出如下。
1 2 3 4 5 6 7 8 9 10 11 12
| 定时任务开始=scheduling-1-2023-06-25T21:26:36.841582700【job1立即执行首次,理论上5s后执行,但是单线程,要等job2,只能排队】 任务{ 1 }已完成,当前时间={ 2023-06-25T21:26:38.844894 }【job2立即执行首次,任务1的延迟2s】 任务{ 2 }已完成,当前时间={ 2023-06-25T21:26:44.842944900 }【job2立即执行首次,任务2的延迟8s】 任务{ 3 }已完成,当前时间={ 2023-06-25T21:26:46.842699200 }【job2立即执行首次,任务3的延迟10s】 定时任务开始=scheduling-1-2023-06-25T21:26:46.842699200【job2完成,job1立刻执行,再次排队】 任务{ 1 }已完成,当前时间={ 2023-06-25T21:26:53.846321200 }【job2等待5s后再次执行,加上任务1的延迟2s,共7s】 任务{ 2 }已完成,当前时间={ 2023-06-25T21:26:59.845000500 }【job2等待5s后再次执行,加上任务2的延迟8s,共13s】 任务{ 3 }已完成,当前时间={ 2023-06-25T21:27:01.844899100 }【job2等待5s后再次执行,加上任务3的延迟10s,共15s】 定时任务开始=scheduling-1-2023-06-25T21:27:01.844899100 任务{ 1 }已完成,当前时间={ 2023-06-25T21:27:08.846885700 } 任务{ 2 }已完成,当前时间={ 2023-06-25T21:27:14.846942200 } 任务{ 3 }已完成,当前时间={ 2023-06-25T21:27:16.847136600 }
|
可见,两个任务的执行时间无法并行,完成后必须等待其他定时任务。若改为 fixedRateString
,则结果为:
1 2 3 4 5 6 7 8 9 10 11 12 13
| 2023-06-25T22:03:37.192+08:00 INFO 20272 --- [ main] com.lab.snippet.SnippetApplication : Started SnippetApplication in 1.151 seconds (process running for 1.417) 任务{ 1 }已完成,当前时间={ 2023-06-25T22:03:39.192069600 } 任务{ 2 }已完成,当前时间={ 2023-06-25T22:03:45.193633600 } 任务{ 3 }已完成,当前时间={ 2023-06-25T22:03:47.192106900 } 定时任务开始=scheduling-1-2023-06-25T22:03:47.192106900 任务{ 1 }已完成,当前时间={ 2023-06-25T22:03:49.193520800 } 任务{ 2 }已完成,当前时间={ 2023-06-25T22:03:55.192690800 } 任务{ 3 }已完成,当前时间={ 2023-06-25T22:03:57.194620 } 定时任务开始=scheduling-1-2023-06-25T22:03:57.194620 任务{ 1 }已完成,当前时间={ 2023-06-25T22:03:59.207931100 } 任务{ 2 }已完成,当前时间={ 2023-06-25T22:04:05.203253700 } 任务{ 3 }已完成,当前时间={ 2023-06-25T22:04:07.205267400 } 定时任务开始=scheduling-1-2023-06-25T22:04:07.205267400
|
此时,job2不在以上一次自己执行结束额时间为准,直接以job1的结束作为基准,2s后开始执行。但还是由于单线程,job1执行完仍然需要排队,等待job2完毕。
改为每5s执行一次,结果不可控
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| 2023-06-25T22:24:53.688+08:00 INFO 14464 --- [ main] com.lab.snippet.SnippetApplication : Started SnippetApplication in 1.135 seconds (process running for 1.386) 任务{ 1 }已完成,当前时间={ 2023-06-25T22:24:57.005405200 } 任务{ 2 }已完成,当前时间={ 2023-06-25T22:25:03.004356200 } 任务{ 3 }已完成,当前时间={ 2023-06-25T22:25:05.006176400 } 定时任务开始=scheduling-1-2023-06-25T22:25:05.006176400 任务{ 1 }已完成,当前时间={ 2023-06-25T22:25:12.016198400 } 任务{ 2 }已完成,当前时间={ 2023-06-25T22:25:18.001606600 } 任务{ 3 }已完成,当前时间={ 2023-06-25T22:25:20.001757800 } 定时任务开始=scheduling-1-2023-06-25T22:25:20.001757800 定时任务开始=scheduling-1-2023-06-25T22:25:25.000208500 任务{ 1 }已完成,当前时间={ 2023-06-25T22:25:27.002035 } 任务{ 2 }已完成,当前时间={ 2023-06-25T22:25:33.013051900 } 任务{ 3 }已完成,当前时间={ 2023-06-25T22:25:35.007493700 } 定时任务开始=scheduling-1-2023-06-25T22:25:35.008453200 定时任务开始=scheduling-1-2023-06-25T22:25:40.001605800 任务{ 1 }已完成,当前时间={ 2023-06-25T22:25:42.003821100 } 任务{ 2 }已完成,当前时间={ 2023-06-25T22:25:48.003383700 } 任务{ 3 }已完成,当前时间={ 2023-06-25T22:25:50.002780300 } 定时任务开始=scheduling-1-2023-06-25T22:25:50.002780300 定时任务开始=scheduling-1-2023-06-25T22:25:55.001144200 任务{ 1 }已完成,当前时间={ 2023-06-25T22:25:57.002327300 }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| 2023-06-25T22:27:40.868+08:00 INFO 8008 --- [ main] com.lab.snippet.SnippetApplication : Started SnippetApplication in 1.142 seconds (process running for 1.386) 定时任务开始=scheduling-1-2023-06-25T22:27:45.000486300 任务{ 1 }已完成,当前时间={ 2023-06-25T22:27:47.005465 } 任务{ 2 }已完成,当前时间={ 2023-06-25T22:27:53.006984800 } 任务{ 3 }已完成,当前时间={ 2023-06-25T22:27:55.006056500 } 定时任务开始=scheduling-1-2023-06-25T22:27:55.007053500 任务{ 1 }已完成,当前时间={ 2023-06-25T22:28:02.009497100 } 任务{ 2 }已完成,当前时间={ 2023-06-25T22:28:08.013432600 } 任务{ 3 }已完成,当前时间={ 2023-06-25T22:28:10.003280800 } 定时任务开始=scheduling-1-2023-06-25T22:28:10.003280800 定时任务开始=scheduling-1-2023-06-25T22:28:15.000889200 任务{ 1 }已完成,当前时间={ 2023-06-25T22:28:17.005103900 } 任务{ 2 }已完成,当前时间={ 2023-06-25T22:28:23.004417100 } 任务{ 3 }已完成,当前时间={ 2023-06-25T22:28:25.002408 } 定时任务开始=scheduling-1-2023-06-25T22:28:25.002408 定时任务开始=scheduling-1-2023-06-25T22:28:30.000387200 任务{ 1 }已完成,当前时间={ 2023-06-25T22:28:32.016138300 } 任务{ 2 }已完成,当前时间={ 2023-06-25T22:28:38.003692100 } 任务{ 3 }已完成,当前时间={ 2023-06-25T22:28:40.002122500 } 定时任务开始=scheduling-1-2023-06-25T22:28:40.002122500 定时任务开始=scheduling-1-2023-06-25T22:28:45.001217
|