在程序开发的过程当中,常常会使用定时任务来实现一些功能,好比:java
在Spring Boot中,咱们可使用@Scheduled注解来快速的实现这些定时任务。git
@Scheduled注解主要支持如下3种方式:github
那么接下来,咱们讲解下具体的实现方式以及这3种方式之间的区别。spring
首先,须要在启动类上添加@EnableScheduling注解:springboot
package com.zwwhnly.springbootdemo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.EnableScheduling; @SpringBootApplication @EnableScheduling public class SpringbootdemoApplication { /*其余代码*/ public static void main(String[] args) { SpringApplication.run(SpringbootdemoApplication.class, args); } }
而后,新建一个定时任务测试类TestSchedule,该类须要添加注解@Componentpost
package com.zwwhnly.springbootdemo; import org.springframework.stereotype.Component; @Component public class TestSchedule { private SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); }
添加一个测试方法testFixedDelay,该方法添加注解@Scheduled,这里咱们先使用最好理解的fixedDelay,为了能看到效果,咱们每隔5秒输出下系统的当前时间,以下所示:测试
// 上次任务执行结束后,间隔5秒再执行下次任务 @Scheduled(fixedDelay = 5000) public void testFixedDelay() { System.out.println("当前时间:" + simpleDateFormat.format(new Date())); }
启动项目,咱们会看到运行结果以下:atom
当前时间:2019-04-09 10:28:56
当前时间:2019-04-09 10:29:01
当前时间:2019-04-09 10:29:06
当前时间:2019-04-09 10:29:11
当前时间:2019-04-09 10:29:16.net
从运行结果来看,确实是每隔5秒输出一次。线程
可是在实际项目中,不可能这么规律,好比方法的执行时间超过了5秒呢(这个应该很常见),那么彼时程序又是如何执行的呢?
咱们修改下程序(代码参考文章[肥朝]原理暂且不谈,定时器你当真会用?):
private List<Integer> index = Arrays.asList(8 * 1000, 3 * 1000, 6 * 1000, 2 * 1000, 2 * 1000); private AtomicInteger atomicInteger = new AtomicInteger(0); // 上次任务执行结束后,间隔5秒再执行下次任务 @Scheduled(fixedDelay = 5000) public void testFixedDelay() throws Exception { int i = atomicInteger.get(); if (i < 5) { Integer sleepTime = index.get(i); System.out.println("当前时间:" + simpleDateFormat.format(new Date())); Thread.sleep(sleepTime); atomicInteger.getAndIncrement(); } }
此时的运行结果为:
当前时间:2019-04-18 14:09:26
当前时间:2019-04-18 14:09:39
当前时间:2019-04-18 14:09:47
当前时间:2019-04-18 14:09:58
当前时间:2019-04-18 14:10:05
让咱们来分析下运行结果:
第2次输出的时间距离第1次输出的时间间隔为13秒(即方法的执行时间8s+定义的延迟时间5s)。
第3次输出的时间距离第2次输出的时间间隔为8秒(即方法的执行时间3s+定义的延迟时间5s)。
第4次输出的时间距离第3次输出的时间间隔为11秒(即方法的执行时间6s+定义的延迟时间5s)。
第5次输出的时间距离第4次输出的时间间隔为7秒(即方法的执行时间2s+定义的延迟时间5s)。
由此咱们能够得出结论:使用fixedDelay,无论方法的执行时间是否超过定义的时间5s,上一个任务执行完成后,都会延迟5s再执行下一个任务。
添加测试方法testFixedRate,此次咱们使用fixedRate。
// 每5秒执行一次 @Scheduled(fixedRate = 5000) public void testFixedRate() { System.out.println("当前时间:" + simpleDateFormat.format(new Date())); }
启动项目,咱们会看到运行结果以下:
从运行结果来看,也是每隔5秒输出一次。
可是在实际项目中,不可能这么规律,好比方法的执行时间超过了5秒呢(这个应该很常见),那么彼时程序又是如何执行的呢?
咱们来修改下程序,让方法每次的执行时间不固定:
private List<Integer> index = Arrays.asList(8 * 1000, 3 * 1000, 6 * 1000, 2 * 1000, 2 * 1000); private AtomicInteger atomicInteger = new AtomicInteger(0); @Scheduled(fixedRate = 5000) public void testFixedRate() throws Exception { int i = atomicInteger.get(); if (i < 5) { Integer sleepTime = index.get(i); System.out.println("当前时间:" + simpleDateFormat.format(new Date())); Thread.sleep(sleepTime); atomicInteger.getAndIncrement(); } }
此时的运行结果为:
当前时间:2019-04-18 15:05:46
当前时间:2019-04-18 15:05:54
当前时间:2019-04-18 15:05:57
当前时间:2019-04-18 15:06:03
当前时间:2019-04-18 15:06:06
以前个人理解是上一个任务执行完成后,会当即执行下一个任务,可是细心的网友会发现,最后一次输出的时间明明离上次的时间间隔了3秒,按照当即执行的说法,应该间隔2秒才对。
直到看到这篇文章[肥朝]原理暂且不谈,定时器你当真会用?,我才发现本身以前的理解是错误的,在此也很是感谢这篇文章的做者肥朝。
文章中的例子很是形象的解释了以上结果的缘由,这里借鉴下(若有侵权,可联系我删除)。
拿洗澡的这个例子来讲。
你能够理解为舍长预算每一个同窗洗澡的时间是5秒。
可是第一个同窗进去洗澡,用了8秒。
第二个同窗原本应该在第5秒的时候就进去的,结果第一个同窗出来的时候,已是第8秒了,那么舍长就赶忙催第二个同窗进去,把时间进度追回来。
第二个同窗知道时间紧,洗了3秒就出来.此时舍长发现,第三个同窗,本来应该是在第10秒进去的,结果如今已经到了第11秒(8+3),那么就赶忙催第三个同窗进去。
第三个同窗沉醉其中,难以自拔的洗了6秒.出来的时候已是第17秒(8+3+6).舍长掐指一算,发现第四个同窗本来应该是第15秒的时候就进去了.结果如今都17秒了,时间不等人,催促第四个同窗进去赶忙洗。
第四个同窗只洗了2秒就出来了,出来的时候,舍长看了一下时间,是第19秒.有原则的舍长发现,第5个同窗本来预算是20秒的时候进去的,结果如今才19秒,不行,那让他在外面玩1秒的手机,等20秒的时候再进去。
相比于上面讲的两种方式,cron表达式显得更加灵活,由于它基本知足各类场景的配置需求,好比固定频率执行,固定某个时间点执行等。
首先,咱们使用cron表达式实现上述例子中的每隔5秒执行一次:
@Scheduled(cron = "0/5 * * * * ?") public void testCron() { System.out.println("当前时间:" + simpleDateFormat.format(new Date())); }
运行结果为:
当前时间:2019-04-09 11:00:50
当前时间:2019-04-09 11:00:55
当前时间:2019-04-09 11:01:00
当前时间:2019-04-09 11:01:05
当前时间:2019-04-09 11:01:10
修改下代码,让每次方法执行时延迟不一样的时间:
private List<Integer> index = Arrays.asList(8 * 1000, 3 * 1000, 6 * 1000, 2 * 1000, 2 * 1000); private AtomicInteger atomicInteger = new AtomicInteger(0); @Scheduled(cron = "0/5 * * * * ?") public void testCron() throws Exception { int i = atomicInteger.get(); if (i < 5) { Integer sleepTime = index.get(i); System.out.println("当前时间:" + simpleDateFormat.format(new Date())); Thread.sleep(sleepTime); atomicInteger.getAndIncrement(); } }
此时的运行结果为:
当前时间:2019-04-18 16:38:10
当前时间:2019-04-18 16:38:20
当前时间:2019-04-18 16:38:25
当前时间:2019-04-18 16:38:35
当前时间:2019-04-18 16:38:40
也许你会发现,有些时间间隔为10s,有些时间间隔为5s,这是为何呢?
这里再次借鉴下肥朝大佬文章中的解释(若有侵权,可联系我删除):
仍然拿洗澡的这个例子来讲。
你能够理解为5s就是一个周期.这就至关于在宿舍洗澡,由于只有一个洗澡位置(单线程),因此每次只能进去一我的,而后舍长在门口,每5s看一下有没有空位,有空位的话叫下一个进去洗澡。
第5秒的时候,舍长看了一下,发现第一个同窗尚未出来(由于他洗了8s)。
第二个周期,也就是第10秒的时候再看一下.发现已经有空位了,那么就叫第二个同窗进去洗。
第三个周期,也就是15秒的时候,又瞄了一眼,发现有空位了(由于前两位同窗用了8+3秒),叫第三个同窗进去洗。
第四个周期,也就是20秒的时候,发现没有空位。
第五个周期的时候,也就是25秒的时候.发现有空位了,接着叫下一个进去洗.剩下的再也不分析。
若是想要设置成天天的某个时间点执行,好比个人我的博客http://www.zwwhnly.com/就是每晚20:00定时拉取GitHub代码并使用Jekyll编译到Nginx的目录下实现的自动发布。
实现这个配置的cron表达式为:0 0 20 * * ? 。
更多的cron表达式,能够到http://cron.qqe2.com/去自定义,勾选好会自动生成cron表达式,很是方便。
https://github.com/zwwhnly/springbootdemo.git,欢迎你们下载,有问题能够多多交流。