[Spring] @Scheduled默认单线程解决方案

@Scheduled默认单线程

在 Spring 框架中,@Scheduled注解默认使用单线程的任务调度器(TaskScheduler),这会导致多个定时任务只能串行执行,一个任务阻塞会影响其他所有任务。

核心思路是自定义一个多线程的TaskSchedulerScheduledExecutorService,替代 Spring 默认的单线程调度器。

解决方案:配置多线程任务调度器

方案 1:直接配置 ThreadPoolTaskScheduler(推荐)

通过创建ThreadPoolTaskScheduler类型的 Bean,指定线程池参数,Spring 会自动使用该调度器执行所有@Scheduled任务。

java 复制代码
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;

@Configuration
@EnableScheduling // 启用定时任务
public class ScheduledConfig {

    @Bean
    public TaskScheduler taskScheduler() {
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
        // 设置核心线程数(根据任务数量调整,一般5-10即可)
        scheduler.setPoolSize(5);
        // 设置线程名称前缀(方便日志排查)
        scheduler.setThreadNamePrefix("scheduled-task-");
        // 任务执行完后是否关闭线程池
        scheduler.setWaitForTasksToCompleteOnShutdown(true);
        // 等待任务完成的超时时间(秒)
        scheduler.setAwaitTerminationSeconds(60);
        scheduler.initialize();
        return scheduler;
    }
}

方案 2:实现 SchedulingConfigurer 接口(更灵活)

通过实现SchedulingConfigurer接口,自定义任务注册器的线程池,适合需要更细粒度配置的场景。

java 复制代码
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import java.util.concurrent.Executors;

@Configuration
@EnableScheduling
public class ScheduledConfig implements SchedulingConfigurer {

    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        // 创建一个固定大小的线程池(5个线程)
        taskRegistrar.setScheduler(Executors.newScheduledThreadPool(5, 
            r -> {
                Thread thread = new Thread(r);
                thread.setName("scheduled-task-"); // 线程名称前缀
                thread.setDaemon(false); // 非守护线程(避免主线程退出后被强制终止)
                return thread;
            }));
    }
}

关键配置说明

  1. 线程池大小(PoolSize)
    根据定时任务的数量和执行频率调整,避免线程过多导致资源浪费,或线程过少仍存在排队问题(建议 5-20,视业务复杂度而定)。
  2. 线程名称前缀
    方便在日志中区分不同任务的线程,便于问题排查。
  3. 任务关闭策略
    • setWaitForTasksToCompleteOnShutdown(true):应用关闭时,等待所有任务执行完成再关闭线程池。
    • setAwaitTerminationSeconds(60):设置等待超时时间,防止任务无限阻塞。

注意事项

  • 配置后,所有@Scheduled任务会并行执行(受限于线程池大小),需确保任务本身是线程安全的(避免共享资源竞争)。
  • 若部分任务需要单独的线程池(如高优先级任务),可通过@Async结合自定义线程池实现更灵活的调度。

通过以上配置,即可解决@Scheduled默认单线程的问题,让定时任务能够并行高效执行。