标题:日常问题记录: Spring @Scheduled任务不生效
问题描述
在维护一个Spring Boot应用程序的过程中,我们发现一些使用@Scheduled
注解的方法并没有按预期执行。而其中一个@Scheduled
注解的代码却触发了。
问题分析
当前问题排查
经过深入调查,我们发现问题的根本原因在于Spring默认的调度器是一个单线程的SimpleAsyncTaskExecutor
。而那个可以正常触发的代码是一个有while循环的常驻任务,它在条件不通过的情况下,将一直占用当前线程,导致其他定时任务都不能执行。
问题延伸
当有多个定时任务需要执行时,如果一个任务执行时间过长,它会阻塞后续任务的执行。此外,如果应用程序的其他部分对线程资源有较高的需求,单一的调度线程可能不足以满足所有需求。
解决方案
为了解决这个问题,我们决定自定义@Scheduled
任务的调度器,以便能够更好地控制线程资源的使用。我们创建了一个@Configuration
类,实现了SchedulingConfigurer
接口,并重写了configureTasks
方法。
自定义调度器
在ScheduleConfig
类中,我们首先通过反射获取了所有带有@Scheduled
注解的方法,然后根据这些方法的数量来设置线程池的大小。这样可以确保每个定时任务都有一个专用的线程来执行,从而避免了任务之间的相互阻塞。
代码实现
@Configuration
public class ScheduleConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
// 获取所有带有@Scheduled注解的方法
Method[] methods = BatchProperties.Job.class.getMethods();
int defaultPoolSize = 3; // 默认的线程池大小
int corePoolSize = 0; // 核心线程池大小
// 计算需要的线程数
if (methods.length > 0) {
for (Method method : methods) {
Scheduled annotation = method.getAnnotation(Scheduled.class);
if (annotation != null) {
corePoolSize++;
}
}
}
// 如果计算出的核心线程数小于默认值,则使用默认值
if (defaultPoolSize > corePoolSize) {
corePoolSize = defaultPoolSize;
}
// 设置自定义的调度器
taskRegistrar.setScheduler(Executors.newScheduledThreadPool(corePoolSize));
}
}
在上述代码中,我们首先定义了一个默认的线程池大小defaultPoolSize
。然后,我们遍历所有带有@Scheduled
注解的方法,根据注解的方法数量来确定核心线程池的大小corePoolSize
。最后,我们使用Executors.newScheduledThreadPool
创建了一个自定义的调度器,并将其设置给ScheduledTaskRegistrar
。
结论
在之后的开发中如还有场景使用@Scheduled的话,一定要考虑线程的占用问题,以及是否有必要自定义调度器。