定时任务
什么是定时任务?
可以自动在项目中根据设定的时长定期执行对应的操作
实现方式
Spring 3.0 版本之后自带定时任务,提供了@EnableScheduling注解和@Scheduled注解来实现定时任务功能。
使用SpringBoot创建定时任务非常简单,目前主要有以下三种创建方式:
1、基于注解(@Scheduled)
2、基于注解设定多线程定时任务
1、基于注解(@Scheduled)
1.1 @Scheduled和@EnableScheduling
基于注解@Scheduled默认为单线程,开启多个任务时,任务的执行时机会受上一个任务执行时间的影响。
@EnableScheduling注解: 在配置类上使用,开启计划任务的支持(类上)。
@Scheduled注解: 来声明这是一个任务,包括 cron,fixDelay,fixRate 等类型(方法上,需先开启计划任务的支持)。
1.1.1开启定时任务
SpringBoot 项目在项目启动类上添加 @EnableScheduling 注解即可开启定时任务管理。
1.1.2添加定时任务
1.1.2.1 fixedRate
finexRate:间隔的时间(单位为毫秒)
这时候就可以在下方定义测试方法,添加@Scheduling注解,开启一个定时方法
此时上面的方法每五秒执行一次,下面的方法每10秒执行一次,这个时候我们运行看一下控制台会输出什么
此时可以看到,在启动后立马执行了第一次,而第一次执行的顺序也是随机的,后面的每次执行都是根据定义的时长来进行轮流执行
如果遇到两个方法同一时间执行,则会根据谁的间隔时间短来判断,故上方是(朵拉)先执行
1.1.2.2 cron
该参数接收一个cron表达式,cron表达式是一个字符串,字符串以5或6个空格隔开,分开共6或7个域,每一个域代表一个含义。
cron 表达式语法:
格式:[秒] [分] [小时] [日] [月] [周] [年]
序号 | 说明 | 是否必填 | 允许填写的值 | 允许的通配符 |
---|---|---|---|---|
1 | 秒 | 是 | 0-59 | , - * / |
2 | 分 | 是 | 0-59 | , - * / |
3 | 小时 | 是 | 0-23 | , - * / |
4 | 日 | 是 | 1-31 | , - * ? / L W |
5 | 月 | 是 | 1-12 or JAN-DEC | , - * / |
6 | 周 | 是 | 1-7 or SUN-SAT | , - * ? / L # |
7 | 年 | 否 | empty 或 1970-2099 | , - * / |
通配符说明:
*表示所有值. 例如:在分的字段上设置 "*",表示每一分钟都会触发。
? 表示不指定值。使用的场景为不需要关心当前设置这个字段的值。例如:要在每月的10号触发一个操作,但不关心是周几,所以需要周位置的那个字段设置为"?" 具体设置为 0 0 0 10 * ?
-表示区间。例如 在小时上设置 "10-12",表示 10,11,12点都会触发。
, 表示指定多个值,例如在周字段上设置 "MON,WED,FRI" 表示周一,周三和周五触发
/ 用于递增触发。如在秒上面设置"5/15" 表示从5秒开始,每增15秒触发(5,20,35,50)。在月字段上设置'1/3'所示每月1号开始,每隔三天触发一次。
L 表示最后的意思。在日字段设置上,表示当月的最后一天(依据当前月份,如果是二月还会依据是否是润年[leap]), 在周字段上表示星期六,相当于"7"或"SAT"。如果在"L"前加上数字,则表示该数据的最后一个。例如在周字段上设置"6L"这样的格式,则表示“本月最后一个星期五"
W 表示离指定日期的最近那个工作日(周一至周五). 例如在日字段上设置"15W",表示离每月15号最近的那个工作日触发。如果15号正好是周六,则找最近的周五(14号)触发, 如果15号是周未,则找最近的下周一(16号)触发.如果15号正好在工作日(周一至周五),则就在该天触发。如果指定格式为 "1W",它则表示每月1号往后最近的工作日触发。如果1号正是周六,则将在3号下周一触发。(注,"W"前只能设置具体的数字,不允许区间"-").
# 序号(表示每月的第几个周几),例如在周字段上设置"6#3"表示在每月的第三个周六.注意如果指定"#5",正好第五周没有周六,则不会触发该配置(用在母亲节和父亲节再合适不过了)
现在,我们逐个解析
(1)*
任何地方都可使用,表示所有
现在就实现了每秒提醒朵拉吃饭,这样可以保证朵拉的饮食,防止变瘦
(2)?
仅可以在日期和星期几中使用,表示不在乎时间
此图是实现在每月的第一天上午10点运行,但由于一个月只能实现一次,故目前无控制台输出
例如:八月份的1号是周一,但九月份的1号不是周一,但是使用了?就可以实现每月一号都输出
(3)/
任何地方都可使用,表示多久一次
朵拉每时每刻都能提醒吃饭,此时李勇杰不乐意了,为什么朵拉可以有人提醒,而自己没有,所以现在来提醒李勇杰吃东西
如果是上方朵拉的提醒方式,那么李勇杰就会撑死,所以我们要对他进行区间提醒,防止李勇杰被撑死了
此时可以看到,这时候我们使用传统的方式,李勇杰就会被撑死
那么肯定不能设定这么短的提醒时间
这个时候就可以实现从0点开始,每隔6小时提醒一次(时间过长不展示)
(4)-
任何地方都可使用,表示区间
上方的/是隔多久执行一次,并不能准确到某个时间点,如果要实现在9点到22点之间每个小时都能提醒
就可以 使用到区间
上方这张图表示从9点到22点之间,并且每分钟的0秒到30秒之间,每秒都提醒一次
此时控制台就会根据间隔时间输出
(5),
在任何地方都可以使用、表示多种选择
如果此时想自定义的提醒,而不是根据指定间隔来提醒,这样就可以使用,来实现
此图表达的意思是:在9-22点时间内,每执行到0,10,15,30,32,38,40,48,55秒的时候,就会给出提醒,这样就可以实现无规则的提醒
这里就可以看到每次到了指定的时间,就会提醒一次,这样就实现了无规则提醒
下方是一些经典案例
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分每分触发 |
0 0/5 14 * * ? | 每天下午的 2点到2点59分(整点开始,每隔5分触发) |
0 0-5 14 * * ? | 每天下午的 2点到2点05分每分触发 |
0 10,44 14 ? 3 WED | 3月分每周三下午的 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 | 从2002年到2005年每月最后一周的星期五的10点15分触发 |
0 15 10 ? * 6#3 | 每月的第三周的星期五开始触发 |
0 0 12 1/5 * ? | 每月的第一个中午开始每隔5天触发一次 |
0 11 11 11 11 ? | 每年的11月11号 11点11分触发(光棍节) |
另外,cron属性接收的cron表达式支持占位符。eg:
配置文件:
time:
cron: */5 * * * * *
interval: 5
每5秒执行一次:
@Scheduled(cron="${time.cron}")
void testPlaceholder1() {
System.out.println("Execute at " + System.currentTimeMillis());
}
@Scheduled(cron="*/${time.interval} * * * * *")
void testPlaceholder2() {
System.out.println("Execute at " + System.currentTimeMillis());
}
结果如下:
1.1.2.3 zone
时区,接收一个 java.util.TimeZone#ID。cron表达式会基于该时区解析。默认是一个空字符串,即取服务器所在地的时区。比如我们一般使用的时区Asia/Shanghai。该字段我们一般留空。
1.1.2.4 fixedDelay
上一次执行完毕时间点之后多长时间再执行。如:
@Scheduled(fixedDelay = 5000) //上一次执行完毕时间点之后5秒再执行
1.1.2.5 fixedDelayString
与 1.2.4 fixedDelay 意思相同,只是使用字符串的形式。唯一不同的是支持占位符。如:
@Scheduled(fixedDelayString = "5000") //上一次执行完毕时间点之后5秒再执行
占位符的使用:
在 application.yml 配置文件中添加如下配置:
time:
fixedDelay: 5000
编写相关代码:
@EnableScheduling
@SpringBootApplication
public class DemoApplication
{
@Scheduled(fixedDelayString = "${time.fixedDelay}")
public void test2() {
System.out.println("李勇杰,该吃*了" + DateFormat.getDateTimeInstance().format(new Date()));
}
}
执行结果:
1.1.2.6 fixedRateString
与 1.2.1 fixedRate 意思相同,只是使用字符串的形式。唯一不同的是支持占位符。
1.1.2.7 initialDelay
第一次延迟多长时间后再执行。如:
@Scheduled(initialDelay=1000, fixedRate=5000) //第一次延迟1秒后执行,之后按fixedRate的规则每5秒执行一次
1.1.2.8 initialDelayString
与 1.2.7 initialDelay 意思相同,只是使用字符串的形式。唯一不同的是支持占位符。
2、基于注解设定多线程定时任务
创建多线程定时任务。
package com.pjb.Task;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
/**
* 基于注解设定多线程定时任务
* @author pan_junbiao
*/
@Component
@EnableScheduling // 1.开启定时任务
@EnableAsync // 2.开启多线程
public class MultithreadScheduleTask
{
@Async
@Scheduled(fixedDelay = 1000) //间隔1秒
public void first() throws InterruptedException {
System.out.println("第一个定时任务开始 : " + LocalDateTime.now().toLocalTime() + "\r\n线程 : " + Thread.currentThread().getName());
System.out.println();
Thread.sleep(1000 * 10);
}
@Async
@Scheduled(fixedDelay = 2000)
public void second() {
System.out.println("第二个定时任务开始 : " + LocalDateTime.now().toLocalTime() + "\r\n线程 : " + Thread.currentThread().getName());
System.out.println();
}
}
注意:由于基于注解@Scheduled默认为单线程,开启多个任务时,任务的执行时机会受上一个任务执行时间的影响。所以这里使用 @Async 注解很关键。
从控制台可以看出,第一个定时任务和第二个定时任务互不影响;
并且,由于开启了多线程,第一个任务的执行时间也不受其本身执行时间的限制,所以需要注意可能会出现重复操作导致数据异常。