java-建立定时任务

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