1.前言:
@Scheduled 参数可以接受两种定时的设置,一种是我们常用的cron="*/6 * * * * ?",一种是 fixedRate = 6000,两种都表示每隔六秒打印一下内容:
@Scheduled(cron = “0 0 0 * * ?”) // 每日凌晨
@Scheduled(cron = “0 0/10 * * * ?”) // 每10分钟
@Scheduled(cron = “0 30 8-18 ? * MON-FRI”) //每周一到周五,8:30到18:30
@Scheduled(fixedDelay = 5000) :上一次执行完毕时间点之后5秒再执行
fixedDelay:在上一次执行完之后等xxx毫秒(xxx就是fixedDelay = 5000中的5000)再执行,循环下去,上一次执行多久都没关系 ,反正上一次执行完后xxx毫秒我才执行
@Scheduled(fixedDelay = 5000, initialDelay=8000)
initialDelay:第一次运行次要等xxx毫秒才能执行,比如:假设原来是14:00:00 开始执行这个,但是设置了initialDelay=8000,那么要在8000毫秒后才能执行这个方法,也就是14:00:08才真正执行
@Scheduled(fixedRate = 5000) :上一次开始执行时间点之后5秒再执行
fixedRate:这个有点蛋疼…,举个例子:比如:假设有5个执行时间点 间隔是5000毫秒:分别是:
T1:14:00:00
T2:14:00:05
T3:14:00:10
T4:14:00:15
T5:14:00:20
如果T1执行时间花了4秒,也就是到了14:00:04,那么你会看到14:00:05分就开始执行了T2,很正常,此时T1结束时间和T2开始时间只差1000毫秒,没毛病
如果T1执行时间花了8秒,怎么办?这时T1执行完的时间是14:00:08,已经覆盖了T2的时间,T2在14:00:05到14:00:08是等等状态。现在是14:00:08,看起来接着是T3执行,
但实际不是,而是立马执行T2,立马执行T2,立马执行T2(T2说:我不管,T1搞我超时了,我无论也是执行),这时你会发现T2的执行时间(也就是第2次执行时间 )是:14:00:08,真的是立马。。。
如此类推,只要时执行时间被覆盖了的,到它了就立马执行
@Scheduled(initialDelay=8000, fixedRate=5000) :第一次延迟8秒后执行,之后按fixedRate的规则每5秒执行一次
2.pom包配置:
pom包里面只需要引入springboot starter包即可
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.0.RELEASE</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
</dependencies>
注意:如果在Windows上运行SpingBoot上面的pom.xml的配置即可,但是要打成jar包上传到Linux上运行的话还需要添加如下配置
<build>
<finalName>SpringBootTest</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
3.启动类启用定时:
在启动类上面加上@EnableScheduling即可开启定时
@SpringBootApplication
@EnableScheduling
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
4.创建定时任务实现类:
定时任务1:
@Component
public class test1Task {
private int count=0;
@Scheduled(cron="*/6 * * * * ?")
private void process(){
System.out.println("this is scheduler task runing "+(count++));
}
}
定时任务2:
@Component
public class test2Task {
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
@Scheduled(fixedRate = 6000)
public void reportCurrentTime() {
System.out.println("现在时间:" + dateFormat.format(new Date()));
}
}
运行结果:
this is scheduler task runing 0
现在时间:14:23:17
this is scheduler task runing 1
现在时间:14:23:23
this is scheduler task runing 2
现在时间:14:23:29
this is scheduler task runing 3
现在时间:14:23:35
5.补充:cron表达式
字段 |
允许值 |
允许的特殊字符 |
备注 |
秒 |
0-59 |
, - * / |
标准实现不支持此字段。linux crontab 命令就不支持,最小的执行时间是一分钟,如果非想实现秒级别的可参考:linux crontab 实现每秒执行
|
分 |
0-59 |
, - * / |
|
小时 |
0-23 |
, - * / |
|
日期 |
1-31 |
, - * ? / L W C |
需要考虑月的天数,如有28天或30天的,? L W只有部分软件实现了 |
月份 |
1-12 或者 JAN-DEC |
, - * / |
|
星期 |
1-7 或者 SUN-SAT |
, - * ? / L C # |
? L W只有部分软件实现了。Linux和Spring的允许值为0-7,0和7为周日;Quartz的允许值为1-7,1为周日 |
年(可选) |
留空, 1970-2099 |
, - * / |
标准实现不支持此字段。linux crontab 命令就不支持 |
其中每个元素可以是一个值(如6),一个连续区间(9-12),一个间隔时间(8-18/4)(/表示每隔4小时),一个列表(1,3,5),通配符。由于"月份中的日期"和"星期中的日期"这两个元素互斥的,必须要对其中一个设置?
"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" 2002年至2005年的每月的最后一个星期五上午10:15触发
"0 15 10 ? * 6#3" 每月的第三个星期五上午10:15触发
有些子表达式能包含一些范围或列表
例如:子表达式(天(星期))可以为 “MON-FRI”,“MON,WED,FRI”,“MON-WED,SAT”
“*”字符代表所有可能的值
因此,“*”在子表达式(月)里表示每个月的含义,“*”在子表达式(天(星期))表示星期的每一天
“/”字符用来指定数值的增量
例如:在子表达式(分钟)里的“0/15”表示从第0分钟开始,每15分钟
在子表达式(分钟)里的“3/20”表示从第3分钟开始,每20分钟(它和“3,23,43”)的含义一样
“?”字符仅被用于天(月)和天(星期)两个子表达式,表示不指定值
当2个子表达式其中之一被指定了值以后,为了避免冲突,需要将另一个子表达式的值设为“?”
“L”字符仅被用于天(月)和天(星期)两个子表达式,它是单词“last”的缩写
但是它在两个子表达式里的含义是不同的。
在天(月)子表达式中,“L”表示一个月的最后一天
在天(星期)自表达式中,“L”表示一个星期的最后一天,也就是SAT
如果在“L”前有具体的内容,它就具有其他的含义了
例如:“6L”表示一个月的倒数第6天,“FRIL”表示一个月的最后一个星期五
注意:在使用“L”参数时,不要指定列表或范围,因为这会导致问题
例子:
# 每月的最后1天
0 0 L * * *
说明:
Linux
* * * * *
- - - - -
| | | | |
| | | | +----- day of week (0 - 7) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat
| | | +---------- month (1 - 12) OR jan,feb,mar,apr ...
| | +--------------- day of month (1 - 31)
| +-------------------- hour (0 - 23)
+------------------------- minute (0 - 59)
补充:常用命令
# 编辑定时任务
crontab -e
# 显示定时任务
crontab -l
# 查看执行的日志
cat /var/log/cron
还可以用如下工具验证下次执行的时间,https://tool.lu/crontab/ 非常的方便
6.遇到的坑:
注:有一个需要每个月的最后一天晚上的九点半执行调度任务,本以为这样写@Scheduled(cron="0 38 21 L * ?")
就OK了,结果报错Caused by: java.lang.IllegalStateException: Encountered invalid @Scheduled method 'process': For input string: "L"
原因:错误的根源是 Spring 的表达式只是 cron表达式的子集,它不包含year字段,并且不能使用所有特殊字符,比如L和W,大部分的文档有误导。
解决:
@Scheduled(cron = "0 38 21 28-31 * ?")
public void execute() {
final Calendar c = Calendar.getInstance();
if (c.get(Calendar.DATE) == c.getActualMaximum(Calendar.DATE)) {
//是最后一天
System.out.println("是最后一天,开始执行调度任务");
}
}
// 或者:
@Scheduled(cron = "0 38 21 28-31 * ?")
public void reptilian() {
final Calendar calendar = Calendar.getInstance();
//如果不是最后一天
if (!(calendar.get(Calendar.DATE) == calendar.getActualMaximum(Calendar.DATE))) {
return;
}
// 执行业务逻辑代码
}