问题:

在spring boot 项目中设置了共13个定时任务,突然有一天部分定时任务就不再执行了,重启程序定时任务也不执行,莫名其妙,查了好多资料,以下是关于我查到的关于定时任务突然停掉的一些原因。

排查:

查到了大部分经验说是spring boot中得定时任务都时单线程得,要进行多线程执行;

单线程测试:

package com.example.demo.conf;

import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.TimeUnit;
import static java.lang.Thread.sleep;

@Component
//@EnableAsync //开启多线程
public class SaticScheduleTask {
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

@Async
@Scheduled(cron = "0 */10 * * * ?")
public void jobCron1() {
System.out.println("第1个定时任务开始 : " + LocalDateTime.now().format(FORMATTER) + " 线程 : " + Thread.currentThread().getName());

}
@Async
@Scheduled(cron = "0 */10 * * * ?")
public void jobCron2() {
System.out.println("第2个定时任务开始 : " + LocalDateTime.now().format(FORMATTER) + " 线程 : " + Thread.currentThread().getName());
}

@Async
@Scheduled(cron = "0 */10 * * * ?")
public void jobCron3() {
System.out.println("第3个定时任务开始 : " + LocalDateTime.now().format(FORMATTER) + " 线程 : " + Thread.currentThread().getName());
}

@Async
@Scheduled(cron = "0 */10 * * * ?")
public void jobCron4() {
System.out.println("第4个定时任务开始 : " + LocalDateTime.now().format(FORMATTER) + " 线程 : " + Thread.currentThread().getName());
}

@Async
@Scheduled(cron = "0 */10 * * * ?")
public void jobCron5() {
System.out.println("第5个定时任务开始 : " + LocalDateTime.now().format(FORMATTER) + " 线程 : " + Thread.currentThread().getName());
}

@Async
@Scheduled(cron = "0 0 1 * * ?")
public void jobCron6() {
System.out.println("第6个定时任务开始 : " + LocalDateTime.now().format(FORMATTER) + " 线程 : " + Thread.currentThread().getName());
}

@Async
@Scheduled(initialDelayString = "PT1M", fixedDelayString = "PT30S")
public void jobCron7() {
System.out.println("第7个定时任务开始 : " + LocalDateTime.now().format(FORMATTER) + " 线程 : " + Thread.currentThread().getName());
}

@Async
@Scheduled(cron = "0 0 0 1 11 ?")
public void jobCron8() {
System.out.println("第8个定时任务开始 : " + LocalDateTime.now().format(FORMATTER) + " 线程 : " + Thread.currentThread().getName());
}

@Async
@Scheduled(cron = "0 0 2 * * ?")
public void jobCron9() {
System.out.println("第9个定时任务开始 : " + LocalDateTime.now().format(FORMATTER) + " 线程 : " + Thread.currentThread().getName());

}
@Async
@Scheduled(initialDelayString = "PT1M", fixedDelayString = "PT30S")
public void jobCron10() throws InterruptedException {
System.out.println("第10个定时任务开始 : " + LocalDateTime.now().format(FORMATTER) + " 线程 : " + Thread.currentThread().getName());
TimeUnit.SECONDS.sleep(600);
}
@Async
@Scheduled(initialDelayString = "PT1M", fixedDelayString = "PT1M")
public void jobCron11() throws InterruptedException {
System.out.println("第11个定时任务开始 : " + LocalDateTime.now().format(FORMATTER) + " 线程 : " + Thread.currentThread().getName());
TimeUnit.SECONDS.sleep(120);
}
@Async
@Scheduled(initialDelayString = "PT1M", fixedDelayString = "PT20S")
public void jobCron12() throws InterruptedException {
System.out.println("第12个定时任务开始 : " + LocalDateTime.now().format(FORMATTER) + " 线程 : " + Thread.currentThread().getName());
TimeUnit.SECONDS.sleep(360);
}

@Async
@Scheduled(cron = "0 45 10 * * ?")
public void jobCron13() {
System.out.println("第13个定时任务开始 : " + LocalDateTime.now().format(FORMATTER) + " 线程 : " + Thread.currentThread().getName());

}
}

测试结果:当有多个定时任务时,其中一个耗时比较长会影响其他定时任务执行,执行结果可以看出设定的第13个定时任务为每天10:45执行,到时间并未执行

SpringBoot定时任务 @Scheduled 没有准时执行的原因及解决方案_定时任务

解决方法:

方法一:改为多线程:(经过反复测试,多线程也会出现延迟执行的情况,和SpringBoot版本有关系,测试2.7.6有问题,2.0.9.RELEASE无问题)

package com.example.demo.conf;

import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.TimeUnit;
import static java.lang.Thread.sleep;

@Component
@EnableAsync //开启多线程
public class SaticScheduleTask {
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

@Async
@Scheduled(cron = "0 */10 * * * ?")
public void jobCron1() {
System.out.println("第1个定时任务开始 : " + LocalDateTime.now().format(FORMATTER) + " 线程 : " + Thread.currentThread().getName());

}
@Async
@Scheduled(cron = "0 */10 * * * ?")
public void jobCron2() {
System.out.println("第2个定时任务开始 : " + LocalDateTime.now().format(FORMATTER) + " 线程 : " + Thread.currentThread().getName());
}

@Async
@Scheduled(cron = "0 */10 * * * ?")
public void jobCron3() {
System.out.println("第3个定时任务开始 : " + LocalDateTime.now().format(FORMATTER) + " 线程 : " + Thread.currentThread().getName());
}

@Async
@Scheduled(cron = "0 */10 * * * ?")
public void jobCron4() {
System.out.println("第4个定时任务开始 : " + LocalDateTime.now().format(FORMATTER) + " 线程 : " + Thread.currentThread().getName());
}

@Async
@Scheduled(cron = "0 */10 * * * ?")
public void jobCron5() {
System.out.println("第5个定时任务开始 : " + LocalDateTime.now().format(FORMATTER) + " 线程 : " + Thread.currentThread().getName());
}

@Async
@Scheduled(cron = "0 0 1 * * ?")
public void jobCron6() {
System.out.println("第6个定时任务开始 : " + LocalDateTime.now().format(FORMATTER) + " 线程 : " + Thread.currentThread().getName());
}

@Async
@Scheduled(initialDelayString = "PT1M", fixedDelayString = "PT30S")
public void jobCron7() {
System.out.println("第7个定时任务开始 : " + LocalDateTime.now().format(FORMATTER) + " 线程 : " + Thread.currentThread().getName());
}

@Async
@Scheduled(cron = "0 0 0 1 11 ?")
public void jobCron8() {
System.out.println("第8个定时任务开始 : " + LocalDateTime.now().format(FORMATTER) + " 线程 : " + Thread.currentThread().getName());
}

@Async
@Scheduled(cron = "0 0 2 * * ?")
public void jobCron9() {
System.out.println("第9个定时任务开始 : " + LocalDateTime.now().format(FORMATTER) + " 线程 : " + Thread.currentThread().getName());

}
@Async
@Scheduled(initialDelayString = "PT1M", fixedDelayString = "PT30S")
public void jobCron10() throws InterruptedException {
System.out.println("第10个定时任务开始 : " + LocalDateTime.now().format(FORMATTER) + " 线程 : " + Thread.currentThread().getName());
TimeUnit.SECONDS.sleep(120);
}
@Async
@Scheduled(initialDelayString = "PT1M", fixedDelayString = "PT1M")
public void jobCron11() throws InterruptedException {
System.out.println("第11个定时任务开始 : " + LocalDateTime.now().format(FORMATTER) + " 线程 : " + Thread.currentThread().getName());
TimeUnit.SECONDS.sleep(120);
}
@Async
@Scheduled(initialDelayString = "PT1M", fixedDelayString = "PT20S")
public void jobCron12() throws InterruptedException {
System.out.println("第12个定时任务开始 : " + LocalDateTime.now().format(FORMATTER) + " 线程 : " + Thread.currentThread().getName());
TimeUnit.SECONDS.sleep(360);
}

@Async
@Scheduled(cron = "0 55 10 * * ?")
public void jobCron13() {
System.out.println("第13个定时任务开始 : " + LocalDateTime.now().format(FORMATTER) + " 线程 : " + Thread.currentThread().getName());

}

}

结果:按指定时间执行了,并没有受到其他比较耗时的任务影响

SpringBoot定时任务 @Scheduled 没有准时执行的原因及解决方案_Async_02

方法二(推荐):改为线程池

下面的代码提供了一个线程池大小为13的taskScheduler

package com.example.demo.conf;

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

@Configuration
public class ScheduledTaskConfiguration implements SchedulingConfigurer {

@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
final ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
taskScheduler.setPoolSize(13);
taskScheduler.initialize();
taskRegistrar.setTaskScheduler(taskScheduler);
}

}
package com.example.demo.conf;

import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.TimeUnit;
import static java.lang.Thread.sleep;

@Component
//@EnableAsync //开启多线程
public class SaticScheduleTask {
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

@Async
@Scheduled(cron = "0 */10 * * * ?")
public void jobCron1() {
System.out.println("第1个定时任务开始 : " + LocalDateTime.now().format(FORMATTER) + " 线程 : " + Thread.currentThread().getName());

}
@Async
@Scheduled(cron = "0 */10 * * * ?")
public void jobCron2() {
System.out.println("第2个定时任务开始 : " + LocalDateTime.now().format(FORMATTER) + " 线程 : " + Thread.currentThread().getName());
}

@Async
@Scheduled(cron = "0 */10 * * * ?")
public void jobCron3() {
System.out.println("第3个定时任务开始 : " + LocalDateTime.now().format(FORMATTER) + " 线程 : " + Thread.currentThread().getName());
}

@Async
@Scheduled(cron = "0 */10 * * * ?")
public void jobCron4() {
System.out.println("第4个定时任务开始 : " + LocalDateTime.now().format(FORMATTER) + " 线程 : " + Thread.currentThread().getName());
}

@Async
@Scheduled(cron = "0 */10 * * * ?")
public void jobCron5() {
System.out.println("第5个定时任务开始 : " + LocalDateTime.now().format(FORMATTER) + " 线程 : " + Thread.currentThread().getName());
}

@Async
@Scheduled(cron = "0 0 1 * * ?")
public void jobCron6() {
System.out.println("第6个定时任务开始 : " + LocalDateTime.now().format(FORMATTER) + " 线程 : " + Thread.currentThread().getName());
}

@Async
@Scheduled(initialDelayString = "PT1M", fixedDelayString = "PT30S")
public void jobCron7() {
System.out.println("第7个定时任务开始 : " + LocalDateTime.now().format(FORMATTER) + " 线程 : " + Thread.currentThread().getName());
}

@Async
@Scheduled(cron = "0 0 0 1 11 ?")
public void jobCron8() {
System.out.println("第8个定时任务开始 : " + LocalDateTime.now().format(FORMATTER) + " 线程 : " + Thread.currentThread().getName());
}

@Async
@Scheduled(cron = "0 0 2 * * ?")
public void jobCron9() {
System.out.println("第9个定时任务开始 : " + LocalDateTime.now().format(FORMATTER) + " 线程 : " + Thread.currentThread().getName());

}
@Async
@Scheduled(initialDelayString = "PT1M", fixedDelayString = "PT30S")
public void jobCron10() throws InterruptedException {
System.out.println("第10个定时任务开始 : " + LocalDateTime.now().format(FORMATTER) + " 线程 : " + Thread.currentThread().getName());
TimeUnit.SECONDS.sleep(120);
}
@Async
@Scheduled(initialDelayString = "PT1M", fixedDelayString = "PT1M")
public void jobCron11() throws InterruptedException {
System.out.println("第11个定时任务开始 : " + LocalDateTime.now().format(FORMATTER) + " 线程 : " + Thread.currentThread().getName());
TimeUnit.SECONDS.sleep(120);
}
@Async
@Scheduled(initialDelayString = "PT1M", fixedDelayString = "PT20S")
public void jobCron12() throws InterruptedException {
System.out.println("第12个定时任务开始 : " + LocalDateTime.now().format(FORMATTER) + " 线程 : " + Thread.currentThread().getName());
TimeUnit.SECONDS.sleep(360);
}

@Async
@Scheduled(cron = "0 05 11 * * ?")
public void jobCron13() {
System.out.println("第13个定时任务开始 : " + LocalDateTime.now().format(FORMATTER) + " 线程 : " + Thread.currentThread().getName());

}

}


结果:按指定时间执行了,并没有受到其他比较耗时的任务影响

SpringBoot定时任务 @Scheduled 没有准时执行的原因及解决方案_Async_03


附:分析 @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 the two is resolvable,a local single-threaded default scheduler will be created within the registrar.

这句话意味着, 如果我们不主动配置我们需要的 TaskScheduler, SpringBoot 会默认使用一个单线程的scheduler来处理我们用 @Scheduled 注解实现的定时任务