项目中使用到了定时任务,刚好借这个机会实验了一下定时任务以及定时任务中遇到的现象写下来。
Spring Schedule 提供三种形式的定时任务:
@EnableScheduling
public class SpringSchedulerApplication {
private static final Logger logger = LoggerFactory.getLogger(SpringSchedulerApplication.class);
public static void main(String[] args) {
logger.info("start application...");
SpringApplication.run(SpringSchedulerApplication.class, args);
}
@Scheduled(initialDelay = 1*1000L, fixedDelay = 1 * 1000L)
public void fixedDelay() {
try {
logger.info("--fixedDelay--");
Thread.sleep(1000 * 2);
} catch (InterruptedException e) {
}
}
}
执行结果与预期相符:
2018-05-22 21:34:18.620 INFO 32203 --- [ main] c.t.t.s.SpringSchedulerApplication : Started SpringSchedulerApplication in 0.644 seconds (JVM running for 1.45)
2018-05-22 21:34:19.617 INFO 32203 --- [pool-1-thread-1] c.t.t.s.SpringSchedulerApplication : --fixedDelay--
2018-05-22 21:34:22.623 INFO 32203 --- [pool-1-thread-1] c.t.t.s.SpringSchedulerApplication : --fixedDelay--
2018-05-22 21:34:25.631 INFO 32203 --- [pool-1-thread-1] c.t.t.s.SpringSchedulerApplication : --fixedDelay--
2018-05-22 21:34:28.639 INFO 32203 --- [pool-1-thread-1] c.t.t.s.SpringSchedulerApplication : --fixedDelay--
2018-05-22 21:34:31.649 INFO 32203 --- [pool-1-thread-1] c.t.t.s.SpringSchedulerApplication : --fixedDelay--
2018-05-22 21:34:34.654 INFO 32203 --- [pool-1-thread-1] c.t.t.s.SpringSchedulerApplication : --fixedDelay--
@Scheduled(initialDelay = 1*1000L, fixedRate = 1 * 1000L)
public void fixedRate() {
try {
logger.info("--fixedRate--");
Thread.sleep(1000 * 3);
} catch (InterruptedException e) {
}
}
把上面的fixedDelay换成fixedRate,执行结果与预期不太一样,并不是每1秒钟执行一次。
2018-05-22 21:40:42.659 INFO 32330 --- [ main] c.t.t.s.SpringSchedulerApplication : Started SpringSchedulerApplication in 0.637 seconds (JVM running for 1.329)
2018-05-22 21:40:43.658 INFO 32330 --- [pool-1-thread-1] c.t.t.s.SpringSchedulerApplication : --fixedRate--
2018-05-22 21:40:46.663 INFO 32330 --- [pool-1-thread-1] c.t.t.s.SpringSchedulerApplication : --fixedRate--
2018-05-22 21:40:49.667 INFO 32330 --- [pool-1-thread-1] c.t.t.s.SpringSchedulerApplication : --fixedRate--
2018-05-22 21:40:52.671 INFO 32330 --- [pool-1-thread-1] c.t.t.s.SpringSchedulerApplication : --fixedRate--
因为默认在单线程下工作,实际结果是每隔3秒钟的时间才会执行一次。那问题来了,fixedRate设置的1秒钟都没有调度吗?
修改一下scheduled任务:前3次执行sleep 3秒时间,之后快速执行(sleep 100ms)。
private static volatile int count = 0;
@Scheduled(initialDelay = 1*1000L, fixedRate = 1 * 1000L)
public void fixedRate() {
try {
logger.info("--fixedRate--, count {}", count);
if (count < 2) {
count += 1;
Thread.sleep(1000 * 3);
} else {
Thread.sleep(100);
}
} catch (InterruptedException e) {
}
}
发现scheduled的确是每秒调度一次,只是因为单线程执行,调度的任务都阻塞了。执行完前3次后,21:45:51把阻塞的任务都执行了,之后开始每1秒钟执行一次。
2018-05-22 21:45:44.217 INFO 32469 --- [ main] c.t.t.s.SpringSchedulerApplication : Started SpringSchedulerApplication in 0.635 seconds (JVM running for 1.357)
2018-05-22 21:45:45.217 INFO 32469 --- [pool-1-thread-1] c.t.t.s.SpringSchedulerApplication : --fixedRate--, count 0
2018-05-22 21:45:48.221 INFO 32469 --- [pool-1-thread-1] c.t.t.s.SpringSchedulerApplication : --fixedRate--, count 1
2018-05-22 21:45:51.225 INFO 32469 --- [pool-1-thread-1] c.t.t.s.SpringSchedulerApplication : --fixedRate--, count 2
2018-05-22 21:45:51.327 INFO 32469 --- [pool-1-thread-1] c.t.t.s.SpringSchedulerApplication : --fixedRate--, count 2
2018-05-22 21:45:51.432 INFO 32469 --- [pool-1-thread-1] c.t.t.s.SpringSchedulerApplication : --fixedRate--, count 2
2018-05-22 21:45:51.535 INFO 32469 --- [pool-1-thread-1] c.t.t.s.SpringSchedulerApplication : --fixedRate--, count 2
2018-05-22 21:45:51.637 INFO 32469 --- [pool-1-thread-1] c.t.t.s.SpringSchedulerApplication : --fixedRate--, count 2
2018-05-22 21:45:52.214 INFO 32469 --- [pool-1-thread-1] c.t.t.s.SpringSchedulerApplication : --fixedRate--, count 2
为了保证fixedRate任务真的可以按照设置的速度执行,无疑需要引入异步执行模式,确保schedule调度的任务不会被单线程执行阻塞。这里引入注解@EnableAsync和@Async。
@EnableScheduling
@EnableAsync
public class SpringSchedulerApplication {
private static final Logger logger = LoggerFactory.getLogger(SpringSchedulerApplication.class);
private static volatile int count = 0;
public static void main(String[] args) {
logger.info("start application...");
SpringApplication.run(SpringSchedulerApplication.class, args);
}
@Async
@Scheduled(initialDelay = 1*1000L, fixedRate = 1 * 1000L)
public void fixedRate() {
try {
logger.info("--fixedRate--, count {}", count);
if (count < 2) {
count += 1;
Thread.sleep(1000 * 3);
} else {
Thread.sleep(100);
}
} catch (InterruptedException e) {
}
}
}
执行结果也符合预期,可以每秒执行一次。但是这里的线程池并不是由我们控制,还是让人担心。任务运行前的log也反应了这点:
[pool-1-thread-1] .s.a.AnnotationAsyncExecutionInterceptor : No task executor bean found for async processing: no bean of type TaskExecutor and no bean named ‘taskExecutor’ either
2018-05-22 21:47:01.374 INFO 32600 --- [ main] c.t.t.s.SpringSchedulerApplication : Started SpringSchedulerApplication in 0.641 seconds (JVM running for 1.419)
2018-05-22 21:4721:4721:47:02.389 INFO 32600 --- [cTaskExecutor-1] c.t.t.s.SpringSchedulerApplication : --fixedRate--, count 0
2018-05-22 21:47:03.373 INFO 32600 --- [cTaskExecutor-2] c.t.t.s.SpringSchedulerApplication : --fixedRate--, count 1
2018-05-22 21:47:04.373 INFO 32600 --- [cTaskExecutor-3] c.t.t.s.SpringSchedulerApplication : --fixedRate--, count 2
2018-05-22 21:47:05.373 INFO 32600 --- [cTaskExecutor-4] c.t.t.s.SpringSchedulerApplication : --fixedRate--, count 2
2018-05-22 21:47:06.373 INFO 32600 --- [cTaskExecutor-5] c.t.t.s.SpringSchedulerApplication : --fixedRate--, count 2
2018-05-22 21:47:07.373 INFO 32600 --- [cTaskExecutor-6] c.t.t.s.SpringSchedulerApplication : --fixedRate--, count 2
@Bean
@PostConstruct
public AsyncTaskExecutor taskExecutor() {
threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
threadPoolTaskExecutor.setThreadNamePrefix("pool-thread");
threadPoolTaskExecutor.setCorePoolSize(3);
threadPoolTaskExecutor.setMaxPoolSize(6);
threadPoolTaskExecutor.setDaemon(true);
threadPoolTaskExecutor.setWaitForTasksToCompleteOnShutdown(true);
threadPoolTaskExecutor.initialize();
return threadPoolTaskExecutor;
}
@PreDestroy
public void destroy() {
if (threadPoolTaskExecutor != null) {
threadPoolTaskExecutor.shutdown();
}
}
执行结果符合预期,多线程每秒执行一次
2018-05-22 21:55:33.070 INFO 34738 --- [ main] c.t.t.s.SpringSchedulerApplication : Started SpringSchedulerApplication in 0.778 seconds (JVM running for 1.511)
2018-05-22 21:55:34.074 INFO 34738 --- [ pool-thread1] c.t.t.s.SpringSchedulerApplication : --fixedRate--, count 0
2018-05-22 21:55:35.062 INFO 34738 --- [ pool-thread2] c.t.t.s.SpringSchedulerApplication : --fixedRate--, count 1
2018-05-22 21:55:36.062 INFO 34738 --- [ pool-thread3] c.t.t.s.SpringSchedulerApplication : --fixedRate--, count 2
2018-05-22 21:55:37.062 INFO 34738 --- [ pool-thread3] c.t.t.s.SpringSchedulerApplication : --fixedRate--, count 2
2018-05-22 21:55:38.062 INFO 34738 --- [ pool-thread1] c.t.t.s.SpringSchedulerApplication : --fixedRate--, count 2
以上实验项目都是使用IDEA的Spring Initializr配置的Spring Boot项目,pom.xml设置
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>com.terence.testgroupId>
<artifactId>spring_schedulerartifactId>
<version>0.0.1-SNAPSHOTversion>
<packaging>jarpackaging>
<name>spring_schedulername>
<description>Demo project for Spring Bootdescription>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.0.2.RELEASEversion>
<relativePath/>
parent>
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding>
<java.version>1.8java.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>