spring boot中的定时任务
在spring boot中,简单的任务调度工作可以由@Scheduled注解来完成。
如果需要深入了解spring的调度,最好的方式是先看下官方文档。
一、Spring的任务抽象接口
TaskExecutor接口
TaskExecutor
是任务执行接口,类似于java.util.concurrent.Executor
,该接口只有一个方法execute(Runnable task)
,用于执行任务。
Spring提供了一组TaskExecutor的实现,基本上能满足所有的需求。它们的使用方式跟普通的Spring Bean一样。
TaskScheduler接口
TaskScheduler
接口是定时器的抽象,它的源代码如下。可以看到,该接口包含了一组方法用于指定任务执行的时间。
public interface TaskScheduler {
ScheduledFuture schedule(Runnable task, Trigger trigger);
ScheduledFuture schedule(Runnable task, Date startTime);
ScheduledFuture scheduleAtFixedRate(Runnable task, Date startTime, long period);
ScheduledFuture scheduleAtFixedRate(Runnable task, long period);
ScheduledFuture scheduleWithFixedDelay(Runnable task, Date startTime, long delay);
ScheduledFuture scheduleWithFixedDelay(Runnable task, long delay);
}
二、实操
2.1、定时任务
具体步骤如下:
1. 在应用入口增加@EnableScheduling
@SpringBootApplication
@EnableScheduling
public class Application {
public static void main(String [] args) {
SpringApplication.run(Application.class, args);
}
}
如果应用依赖了
spring-boot-starter-actuator
,即使没有声明@EnableScheduling,调度任务也会被执行。因为spring-boot-starter-actuator
本身也声明了@EnableScheduling,见MetricExportAutoConfiguration。
2. 增加任务调度服务
@Component // 或@Service
public class TaskService {
@Scheduled(fixedRate = 1000) // 每隔1秒执行一次
public void timerRate() {
// 在此处执行调度任务。
}
}
@Scheduled 注解主要使用在执行调度任务的方法上。
注意!Springboot 默认的执行方式是串行执行,无论有多少task(@Scheduled),都是一个线程串行执行。同一个task,如果前一个还没跑完后面一个就不会触发,不同的task也不能同时运行,这是因为scheduler的默认线程数为1的缘故。
在高可用的情况下,如果微服务有多个实例,scheduler会在多个实例上同时运行。
2.2、并行任务
在上面的基础上,增加一个实现SchedulingConfigurer接口的类:
@Configuration
public class ScheduleConfig implements SchedulingConfigurer {
private final int POOL_SIZE = 10;
@Override
public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
threadPoolTaskScheduler.setPoolSize(POOL_SIZE);
// 线程前缀
threadPoolTaskScheduler.setThreadNamePrefix("my-scheduled-task-pool-");
threadPoolTaskScheduler.initialize();
scheduledTaskRegistrar.setTaskScheduler(threadPoolTaskScheduler);
}
/* @Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(taskExecutor());
}
@Bean(destroyMethod="shutdown")
public Executor taskExecutor() {
return Executors.newScheduledThreadPool(POOL_SIZE); // 线程池初始化10个线程
}*/
}
如果在调度方法里打印当前线程名称:
Current Thread : my-scheduled-task-pool-1
Current Thread : my-scheduled-task-pool-2
...
配置多个线程会导致同一个task前一个还没跑完后面又被触发的问题。
附录1、@Scheduled中的参数
(1) cron:cron表达式,指定任务在特定时间执行;
(2) fixedDelay:表示上一次任务执行完成后多久再次执行,参数类型为long,单位ms;
(3) fixedDelayString:与fixedDelay含义一样,只是参数类型变为String;
(4) fixedRate:表示按一定的频率执行任务,参数类型为long,单位ms;
(5) fixedRateString: 与fixedRate的含义一样,只是将参数类型变为String;
(6) initialDelay:表示延迟多久再第一次执行任务,参数类型为long,单位ms;
(7) initialDelayString:与initialDelay的含义一样,只是将参数类型变为String;
(8) zone:时区,默认为当前时区,一般没有用到。
例子:
- @Scheduled(fixedRate=1000): 上一次开始执行后1秒再次执行;
- @Scheduled(fixedDelay=1000):上一次执行完成后1秒再次执行;
- @Scheduled(initialDelay=1000, fixedDelay=3000):第一次延迟1秒执行,然后在上一次执行完成后3秒再次执行;
- @Scheduled(cron="* * * * * ?"):按cron表达式执行。
附录2、CRON表达式
Cron格式
一个 Cron表达式是由6或7个字段(年字段是可选字段)的字符串组成,字段与字段之间用空格来隔开。
Cron表达式使用格式:
Seconds | Minutes | Hours | DayofMonth | Month | DayofWeek | [Year] |
---|---|---|---|---|---|---|
秒 | 分 | 时 | 天 | 月 | 周 | [年] |
各个域的定义:
域 | 必输 | 含义 | 允许值 | 允许特殊符号 |
---|---|---|---|---|
Seconds | 是 | 秒 | 0-59 | , - * / |
Minutes | 是 | 分 | 0-59 | , - * / |
Hours | 是 | 时 | 0-23 | , - * / |
Day of Month | 是 | 天 | 1-31 | , - * ? / L W |
Month | 是 | 月 | 1-12或者 JAN-DEC | , - * / |
Day of Week | 是 | 周 | 1-7 或者 SUN-SAT | , - * ? / L # |
[Year] | 否 | [年] | 空,1970-2099 | , - * / |
特殊符号代表的含义:
符号 | 含义 |
---|---|
* | 匹配该域的任意值;如* 用在分 所在的域,表示每分钟都会触发事件。 |
? | 无特定值。只能用在DayofMonth 和DayofWeek 两个域。 由于DayofMonth 和DayofWeek 这两个元素是互斥的,必须要对其中一个设置? 。例如想在每月的20日触发调度,不管20日到底是星期几,则只能使用如下写法: 13 13 15 20 * ? , 其中最后一位只能用? ,而不能使用* ,如果使用* 表示不管星期几都会触发。 |
- | 匹配一个特定的范围值;如时 所在的域的值是10-12 ,表示10、11、12点的时候会触发事件. |
, | 匹配多个指定的值;如周 所在的域的值是2,4,6 ,表示在周一、周三、周五就会触发事件。(1表示周日,2表示周一,3表示周二,以此类推,7表示周六)。 |
/ | 指定数值的增量。左边是开始触发时间,右边是每隔固定时间触发一次事件,如秒所在的域的值是5/15 ,表示从第5秒开始,每15秒触发,即在第5秒、20秒、35秒、50秒的时候都触发一次事件,等价于“5,20,35,50”。 |
L | last,最后的意思,如果是用在天 这个域,表示月的最后一天,如果是用在周所在的域,如6L ,表示某个月最后一个周五。 |
W | weekday,工作日的意思。如天 所在的域的值是15W ,表示本月15日最近的工作日,如果15日是周六,触发器将触发上14日周五。如果15日是周日,触发器将触发16日周一。如果15日不是周六或周日,而是周一至周五的某一个,那么它就在15日当天触发事件。 |
# | 用来指定每个月的第几个星期几,如6#3 表示某个月的第三个星期五。 |
CRON表达式官方例子
表达式 | 含义 |
---|---|
"0 0 12 * * ?" | 每天12:00触发事件 |
"0 15 10 ? * *" | 每天10:15触发事件 |
"0 15 10 * * ?" | 每天10:15触发事件 |
"0 15 10 * * ? *" | 每天10:15触发事件 |
"0 15 10 * * ? 2005" | 2005年的每天10:15触发事件 |
"0 * 14 * * ?" | 每天14点开始触发,每分钟触发一次,14:59分结束 |
"0 0/5 14 * * ?" | 每天14点开始触发到14:59分结束的每5分钟触发一次事件 |
"0 0/5 14,18 * * ?" | 每天14点开始到14:59期间和18点到18:59期间的每5分钟触发一次事件 |
"0 0-5 14 * * ?" | 每天14点到14:05期间的每1分钟触发一次事件 |
"0 10,44 14 ? 3 WED" | 每年3月的星期三的14:10和14: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" | 2002年至2005年的每月的最后一个星期五10:15触发一次事件 |
"0 15 10 ? * 6#3" | 每月的第三个星期五10:15触发一次事件 |