有时需要服务器定时,或者每隔多长时间去做某件事情,Spring boot使用注解@Scheduled实现该功能。
方法概述
Scheduled共有5个方法
(1)initialDelay:在第一次执行前,需要Delay的时间。
(2)fixedRate:间隔固定的时间执行,不论是上次执行是否完毕。
(3)fixedDelay:在前一次执行后,Delay一段时间,然后再次执行。
(4)cron:固定的时间段执行,共有7个参数。
(5)zone:与cron配合使用,指定所使用的时区。若不设置,则使用当前时区。
需要注意的是,方法中所用到的时间单位为毫秒。
使用@Scheduled的前提条件
(1)需要将Application添加@EnableScheduling
package com.breakloop.taskdemo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@EnableScheduling
public class TaskdemoApplication {
public static void main(String[] args) {
SpringApplication.run(TaskdemoApplication.class, args);
}
}
(2)使用@Scheduled方法的类,必须进行了注册声明。例如@Component
package com.breakloop.taskdemo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
public class myTaskList {
private static Logger logger= LoggerFactory.getLogger(myTaskList.class);
private static final long initDelay=3000l;
private static final long delayFixed =1000l;
@Scheduled(fixedDelay = delayFixed)
public void task1(){
logger.info("task1");
try {
Thread.sleep(1000l);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
下面分别来看几种方法。
fixedDelay
代码中的Task1便使用了fixedDelay。在输出“Task1”后,等待1秒。
执行结果如下
2017-12-06 23:21:53.526 INFO 8636 --- [pool-1-thread-1] com.breakloop.taskdemo.myTaskList : task1
2017-12-06 23:21:53.526 INFO 8636 --- [on(3)-127.0.0.1] c.breakloop.taskdemo.ServletInitializer : Started ServletInitializer in 4.004 seconds (JVM running for 8.255)
[2017-12-06 11:21:53,574] Artifact taskdemo:war: Artifact is deployed successfully
[2017-12-06 11:21:53,574] Artifact taskdemo:war: Deploy took 6,189 milliseconds
2017-12-06 23:21:54.219 INFO 8636 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization started
2017-12-06 23:21:54.250 INFO 8636 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization completed in 31 ms
2017-12-06 23:21:55.532 INFO 8636 --- [pool-1-thread-1] com.breakloop.taskdemo.myTaskList : task1
06-Dec-2017 23:21:57.083 [ContainerBackgroundProcessor[StandardEngine[Catalina]]] org.apache.catalina.startup.HostConfig.deployDirectory Deploying web application directory [C:\D\apache-tomcat-9.0.1\webapps\manager]
06-Dec-2017 23:21:57.161[ContainerBackgroundProcessor[StandardEngine[Catalina]]] org.apache.catalina.startup.HostConfig.deployDirectory Deployment of web application directory [C:\D\apache-tomcat-9.0.1\webapps\manager] has finished in [78] ms
2017-12-06 23:21:57.562 INFO 8636 --- [pool-1-thread-1] com.breakloop.taskdemo.myTaskList : task1
2017-12-06 23:21:59.569 INFO 8636 --- [pool-1-thread-1] com.breakloop.taskdemo.myTaskList : task1
从执行结果,可以得出以下结论
(a)从时间上看,第N+1次执行确实在第N次后,Delay了1秒钟。
(b)第一次执行,在Started ServletInitializer前就开始了。
因此,为了保证必要的初始化完成,使用initialDelay方法是非常有必要的。
initialDelay
我们对Task1的声明进行修改。添加initialDelay方法。
@Scheduled(initialDelay = initDelay,fixedDelay = delayFixed)
public void task1(){
logger.info("task1");
try {
Thread.sleep(1000l);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
执行结果如下
2017-12-06 23:31:01.691 INFO 8380 --- [on(5)-127.0.0.1] c.breakloop.taskdemo.ServletInitializer : Started ServletInitializer in 4.033 seconds (JVM running for 8.209)
[2017-12-06 11:31:01,738] Artifact taskdemo:war: Artifact is deployed successfully
[2017-12-06 11:31:01,738] Artifact taskdemo:war: Deploy took 6,163 milliseconds
2017-12-06 23:31:02.378 INFO 8380 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization started
2017-12-06 23:31:02.409 INFO 8380 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization completed in 31 ms
2017-12-06 23:31:04.687 INFO 8380 --- [pool-1-thread-1] com.breakloop.taskdemo.myTaskList : task1
06-Dec-2017 23:31:05.276 [ContainerBackgroundProcessor[StandardEngine[Catalina]]] org.apache.catalina.startup.HostConfig.deployDirectory Deploying web application directory [C:\D\apache-tomcat-9.0.1\webapps\manager]
06-Dec-2017 23:31:05.348 [ContainerBackgroundProcessor[StandardEngine[Catalina]]] org.apache.catalina.startup.HostConfig.deployDirectory Deployment of web application directory [C:\D\apache-tomcat-9.0.1\webapps\manager] has finished in [72] ms
2017-12-06 23:31:06.689 INFO 8380 --- [pool-1-thread-1] com.breakloop.taskdemo.myTaskList : task1
2017-12-06 23:31:08.701 INFO 8380 --- [pool-1-thread-1] com.breakloop.taskdemo.myTaskList : task1
从结果可见,task1在Artifact is deployed successfully之后才执行,达到预期目的。
fixedRate
在myTaskList中添加Task2,使用fixedRate方法。
同时,为避免启动过程影响执行效果,将initDelay更新未7秒。
@Scheduled(initialDelay = initDelay,fixedRate =delayFixed )
public void task2(){
logger.info("task2");
try {
Thread.sleep(1000l);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
执行结果如下
2017-12-06 23:45:54.443 INFO 7416 --- [pool-1-thread-1] com.breakloop.taskdemo.myTaskList : task2
2017-12-06 23:45:55.444 INFO 7416 --- [pool-1-thread-1] com.breakloop.taskdemo.myTaskList : task1
2017-12-06 23:45:56.444 INFO 7416 --- [pool-1-thread-1] com.breakloop.taskdemo.myTaskList : task2
2017-12-06 23:45:57.453 INFO 7416 --- [pool-1-thread-1] com.breakloop.taskdemo.myTaskList : task2
2017-12-06 23:45:58.462 INFO 7416 --- [pool-1-thread-1] com.breakloop.taskdemo.myTaskList : task2
2017-12-06 23:45:59.476 INFO 7416 --- [pool-1-thread-1] com.breakloop.taskdemo.myTaskList : task1
2017-12-06 23:46:00.477 INFO 7416 --- [pool-1-thread-1] com.breakloop.taskdemo.myTaskList : task2
2017-12-06 23:46:01.479 INFO 7416 --- [pool-1-thread-1] com.breakloop.taskdemo.myTaskList : task2
2017-12-06 23:46:02.479 INFO 7416 --- [pool-1-thread-1] com.breakloop.taskdemo.myTaskList : task2
2017-12-06 23:46:03.493 INFO 7416 --- [pool-1-thread-1] com.breakloop.taskdemo.myTaskList : task2
由结果可以得出以下结论
(a)虽然上次Task2还未执行完,但每秒都会启动一个新的Task2。
(b)由INFO 7416 — [pool-1-thread-1]可知,所有的执行,都是串行的,在同一线程中完成!
那么问题来了,如何让任务并行呢?
并发设置
实现任务的并行,需要以下几个步骤
(1)创建类(本例中为SchedulConfig),添加注释@Configuration
(2)为类添加注释@EnableScheduling
(2)该类实现SchedulingConfigurer接口,并实现方法
public void configureTasks(ScheduledTaskRegistrar taskRegistrar),为Task指定Executor。
全部代码如下
package com.breakloop.taskdemo;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
@Configuration
@EnableScheduling
public class SchedulConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(taskExecutor());
}
public Executor taskExecutor(){
return Executors.newScheduledThreadPool(10);
}
}
从代码中可见,Task的Executor是一个容量为10的线程池。
我们再来执行一下Task1,2
执行结果如下
2017-12-06 23:53:58.318 INFO 8080 --- [pool-1-thread-1] com.breakloop.taskdemo.myTaskList : task2
2017-12-06 23:53:58.318 INFO 8080 --- [pool-1-thread-2] com.breakloop.taskdemo.myTaskList : task1
2017-12-06 23:53:59.324 INFO 8080 --- [pool-1-thread-1] com.breakloop.taskdemo.myTaskList : task2
2017-12-06 23:54:00.325 INFO 8080 --- [pool-1-thread-3] com.breakloop.taskdemo.myTaskList : task1
2017-12-06 23:54:00.325 INFO 8080 --- [pool-1-thread-2] com.breakloop.taskdemo.myTaskList : task2
2017-12-06 23:54:01.332 INFO 8080 --- [pool-1-thread-4] com.breakloop.taskdemo.myTaskList : task2
2017-12-06 23:54:02.346 INFO 8080 --- [pool-1-thread-1] com.breakloop.taskdemo.myTaskList : task1
2017-12-06 23:54:02.346 INFO 8080 --- [pool-1-thread-5] com.breakloop.taskdemo.myTaskList : task2
可见,Task1,2在同一个线程池中的不同线程中运行。
cron
通过以上三种方法,可以有间隔的执行任务。但并不能满足所有需求。例如,服务器只需要在每天的8点到20点间,每10分钟执行一次任务,或者在固定的时间点执行任务。此时,cron便派上用场。
我们先写一下Task3,之后记录cron参数的含义。同时,将delayFixed 延长至20秒,便于观察结果。
@Scheduled(cron = "0/10 45-47 0 * * ?")
public void task3(){
logger.info("task3");
try {
Thread.sleep(1000l);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
执行结果如下图
cron的传参为String类型,由7部分组成,各部分用空格分隔。从前往后含义依次如下
(1)秒,有效值0~59
(2)分钟,有效值0~59
(3)小时,有效值0~23
(4)天数,有效值0~30
(5)月,有效值0~11
(6)星期几,有效值1~7。1是星期天,然后依次是星期一,二…六
(7)年,有效期1970~2099
需要注意的是,参数由(1)至(7)或(1)至(6)组成。
此外,(4)与(7)冲突,因此必须有一个为转义符“?”,表示不确定。
当然,还有其他的转义符。
转义符
“?”,表示不确定。
“*”,表示不做要求。
“,”,表示多个数据。例如“30 29,30,31 0 * * ?”表示每天凌晨0点29分30秒,30分30秒,31分30秒,各执行一次。
“-”,表示区间,0-5,表示[0,5],例如“30 29,30,31 0 * * ?”等同于“30 29-31 0 * * ?”。
“/”,表示间隔,0/5,表示每隔5秒(或者分钟,小时,天,月)。
“L”,表示最后的XXX。例如”0 15 10 ? * 6L 2002-2005” 2002年至2005年的每月的最后一个星期五上午10:15执行。
“#”,表示第几个XXX。例如”0 15 10 ? * 6#3” 每月的第三个星期五上午10:15执行。
因此,Task3中”0/10 45-47 0 * * ?”的含义是, 每天凌晨0点45分至47分之间,每10秒执行。
zone
zone很少被用到,网上的资料也不多。翻看源码,可知其输入为String,跟java.util.TimeZone.getTimeZone(String)的传参应该一致。
此处,未做实验,之后补上。
至此,Schedule总结完成。