Spring Boot定时任务fixedRate与你想的不一样

项目中使用到了定时任务,刚好借这个机会实验了一下定时任务以及定时任务中遇到的现象写下来。

Spring Schedule 提供三种形式的定时任务:

固定延迟时间 @Scheduled(fixedDelay = 时间间隔 )

@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(fixedRate = 时间间隔 )

    @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(fixedRate)任务哪里去了?

修改一下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

@Scheduled(fixedRate)如何保证固定速度执行?

为了保证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

@Scheduled(fixedRate) 设置任务池

    @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>

你可能感兴趣的:(Java)