關於@Scheduled註解的任務為什麼不執行的問題

概述

在SpringBoot中可以通過@Scheduled來註解定義一個定時任務,但是有時候你可能發現有的定時任務道理時間卻沒有執行,但是又不是每次都不執行,為什麼呢???

舉例說明

下面這段diam定義瞭一個沒隔10s執行一次的定時任務:

package com.study.practice.schedule;

import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

/**
 * @Description : schedule測試
 * @Version : V1.0.0
 * @Date : 2021/12/1 11:22
 */
@Component
@Slf4j
public class ScheduleTest {
    @Scheduled(cron = "0/10 * * * * ?")
    public void execute() {
        log.info("Scheduled task is running ... ...");
    }
}

記得在啟動類或者Configuration類上添加瞭@EnableScheduling註解。

啟動應用,控制臺每隔10秒打印一條日志:

2021-12-01 11:27:00.002  INFO 97876 — [   scheduling-1] c.study.practice.schedule.ScheduleTest   : Scheduled task is running … …
2021-12-01 11:27:10.001  INFO 97876 — [   scheduling-1] c.study.practice.schedule.ScheduleTest   : Scheduled task is running … …
2021-12-01 11:27:20.002  INFO 97876 — [   scheduling-1] c.study.practice.schedule.ScheduleTest   : Scheduled task is running … …
2021-12-01 11:27:30.001  INFO 97876 — [   scheduling-1] c.study.practice.schedule.ScheduleTest   : Scheduled task is running … …
2021-12-01 11:27:40.002  INFO 97876 — [   scheduling-1] c.study.practice.schedule.ScheduleTest   : Scheduled task is running … …
2021-12-01 11:27:50.001  INFO 97876 — [   scheduling-1] c.study.practice.schedule.ScheduleTest   : Scheduled task is running … …
2021-12-01 11:28:00.002  INFO 97876 — [   scheduling-1] c.study.practice.schedule.ScheduleTest   : Scheduled task is running … …
2021-12-01 11:28:10.002  INFO 97876 — [   scheduling-1] c.study.practice.schedule.ScheduleTest   : Scheduled task is running … …

但是,問題才剛剛開始。

在上面的相關代碼中,我們使用cron表達式指定的定時任務執行時間點從0秒開始,每隔10s執行一次,現在我們再加一個定時任務:

package com.study.practice.schedule;

import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

/**
 * @Description : schedule測試
 * @Version : V1.0.0
 * @Date : 2021/12/1 11:22
 */
@Component
@Slf4j
public class ScheduleTestTwo {
    @Scheduled(cron = "0/10 * * * * *")
    public void second() throws InterruptedException {
        log.info("Second scheduled task is running... ...");
        TimeUnit.SECONDS.sleep(5);
    }
}

啟動項目,執行結果如下:

2021-12-01 11:36:30.001  INFO 134576 — [   scheduling-1] c.study.practice.schedule.ScheduleTest   : Scheduled task is running … …
2021-12-01 11:36:30.002  INFO 134576 — [   scheduling-1] c.s.practice.schedule.ScheduleTestTwo    : Second scheduled task is running… …
2021-12-01 11:36:40.002  INFO 134576 — [   scheduling-1] c.s.practice.schedule.ScheduleTestTwo    : Second scheduled task is running… …
2021-12-01 11:36:45.002  INFO 134576 — [   scheduling-1] c.study.practice.schedule.ScheduleTest   : Scheduled task is running … …
2021-12-01 11:36:50.001  INFO 134576 — [   scheduling-1] c.s.practice.schedule.ScheduleTestTwo    : Second scheduled task is running… …
2021-12-01 11:36:55.002  INFO 134576 — [   scheduling-1] c.study.practice.schedule.ScheduleTest   : Scheduled task is running … …
2021-12-01 11:37:00.001  INFO 134576 — [   scheduling-1] c.s.practice.schedule.ScheduleTestTwo    : Second scheduled task is running… …
2021-12-01 11:37:05.001  INFO 134576 — [   scheduling-1] c.study.practice.schedule.ScheduleTest   : Scheduled task is running … …

可以看到任務1和任務2本該都是每個10s執行,但是卻發現隻有任務2執行瞭,任務1卻等待瞭5s之後才執行。

原因分析

為瞭找到原因,我們從@Scheduled註解的源碼開始分析:

下面是@Scheduled註解的註釋:

 *
 * <p>Processing of {@code @Scheduled} annotations is performed by
 * registering a {@link ScheduledAnnotationBeanPostProcessor}. This can be
 * done manually or, more conveniently, through the {@code <task:annotation-driven/>}
 * element or @{@link EnableScheduling} annotation.
 *

劃重點, 每一個有@Scheduled註解的方法都會被註冊為一個ScheduledAnnotationBeanPostProcessor, 再接著往下看ScheduledAnnotationBeanPostProcessor:

    /**
     * Set the {@link org.springframework.scheduling.TaskScheduler} that will invoke
     * the scheduled methods, or a {@link java.util.concurrent.ScheduledExecutorService}
     * to be wrapped as a TaskScheduler.
     * <p>If not specified, default scheduler resolution will apply: searching for a
     * unique {@link TaskScheduler} bean in the context, or for a {@link TaskScheduler}
     * bean named "taskScheduler" otherwise; the same lookup will also be performed for
     * a {@link ScheduledExecutorService} bean. If neither of the two is resolvable,
     * a local single-threaded default scheduler will be created within the registrar.
     * @see #DEFAULT_TASK_SCHEDULER_BEAN_NAME
     */
    public void setScheduler(Object scheduler) {
        this.scheduler = scheduler;
    }

重點來瞭,註意這句話:

if neither of two is resolvable, a local single-thread default scheduler will be created within in the registrar.

這句話意味著,如果我們不主動配置我們需要的TaskScheduler,SpringBoot會默認使用一個單線程的scheduler來處理我們用@Scheduled註解實現的定時任務,到此我們剛才的問題就可以理解瞭。

因為是單個線程執行所有的定時任務,所有task2如果先執行,因為執行中等待瞭5s,所以task2執行完後,task1接著繼續執行。

解決方案

搞清楚這個流程後,解決這個問題就很簡單瞭。

根據剛才註釋的描述,我們隻需要提供一個滿足自己需要的TaskScheduler並註冊到context容器中就可以瞭。

package com.study.practice.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;

/**
 * @Description : scheduler配置類
 * @Version : V1.0.0
 * @Date : 2021/12/1 14:00
 */
@Configuration
public class ScheduledTaskConfig implements SchedulingConfigurer {
    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
        taskScheduler.setPoolSize(2);
        taskScheduler.initialize();
        taskRegistrar.setTaskScheduler(taskScheduler);
    }
}

上面的代碼提供瞭一個線程池大小為2的taskScheduler,再次啟動SpringBoot查看效果。

2021-12-01 14:05:20.001  INFO 39768 — [TaskScheduler-2] c.s.practice.schedule.ScheduleTestTwo    : Second scheduled task is running… …
2021-12-01 14:05:20.001  INFO 39768 — [TaskScheduler-1] c.study.practice.schedule.ScheduleTest   : Scheduled task is running … …
2021-12-01 14:05:30.002  INFO 39768 — [TaskScheduler-1] c.study.practice.schedule.ScheduleTest   : Scheduled task is running … …
2021-12-01 14:05:30.002  INFO 39768 — [TaskScheduler-2] c.s.practice.schedule.ScheduleTestTwo    : Second scheduled task is running… …
2021-12-01 14:05:40.001  INFO 39768 — [TaskScheduler-1] c.study.practice.schedule.ScheduleTest   : Scheduled task is running … …
2021-12-01 14:05:40.001  INFO 39768 — [TaskScheduler-2] c.s.practice.schedule.ScheduleTestTwo    : Second scheduled task is running… …

可以看到,當線程池裡有兩個線程時,這兩個任務各種按照預定的時間進行觸發,互不影響瞭。

以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。

推薦閱讀: