Spring Boot中如何动态调度多个可重复执行的定时任务?
Spring Boot 动态多任务调度运行异常解决方案
问题根因
新增任务后旧任务停止的问题,通常是两个常见配置错误导致的:
- 全局只声明了单个
ScheduledFuture变量存储调度任务实例,每次新增任务时会覆盖旧的实例引用,触发旧任务被自动取消 ThreadPoolTaskScheduler默认核心线程数为1,新增任务占用了唯一线程后,旧的周期任务无法获取线程资源执行
解决步骤
1. 配置多线程调度器
首先自定义ThreadPoolTaskScheduler配置,设置足够的核心线程数,适配多任务并行执行需求:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; @Configuration public class SchedulerConfig { @Bean public ThreadPoolTaskScheduler taskScheduler() { ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); // 核心线程数根据预期同时运行的任务数量调整 scheduler.setPoolSize(10); scheduler.setThreadNamePrefix("dynamic-task-"); // 任务执行完毕后允许线程超时回收 scheduler.setWaitForTasksToCompleteOnShutdown(true); scheduler.setAwaitTerminationSeconds(60); return scheduler; } }
2. 用线程安全容器存储所有任务实例
使用ConcurrentHashMap存储所有调度任务的ScheduledFuture实例,避免覆盖旧任务:
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; import org.springframework.scheduling.support.CronTrigger; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ScheduledFuture; @Service public class DynamicTaskService { @Resource private ThreadPoolTaskScheduler taskScheduler; // key为任务唯一ID,value为调度任务实例 private final ConcurrentHashMap<String, ScheduledFuture<?>> taskMap = new ConcurrentHashMap<>(); /** * 新增周期任务 * @param taskId 任务唯一标识 * @param cron 任务执行cron表达式 * @param task 任务执行逻辑 */ public void addTask(String taskId, String cron, Runnable task) { // 任务去重:如果同ID任务已存在,先取消旧任务 if (taskMap.containsKey(taskId)) { cancelTask(taskId); } // 包装任务,增加异常捕获避免单个任务异常影响线程复用 Runnable wrapTask = () -> { try { task.run(); } catch (Exception e) { // 自定义异常处理逻辑,比如日志打印 e.printStackTrace(); } }; // 提交调度任务 ScheduledFuture<?> future = taskScheduler.schedule(wrapTask, new CronTrigger(cron)); // 存入容器 taskMap.put(taskId, future); } /** * 取消指定任务 * @param taskId 任务唯一标识 */ public void cancelTask(String taskId) { ScheduledFuture<?> future = taskMap.get(taskId); if (future != null) { future.cancel(true); taskMap.remove(taskId); } } }
3. 调用示例
在接收用户输入的Controller层调用上述服务,即可实现多任务独立运行:
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; @RestController public class TaskController { @Resource private DynamicTaskService dynamicTaskService; @PostMapping("/addTask") public String addTask(@RequestParam String taskId, @RequestParam String cron) { // 这里可以替换为用户自定义的任务逻辑 Runnable task = () -> System.out.println("任务" + taskId + "执行,当前时间:" + System.currentTimeMillis()); dynamicTaskService.addTask(taskId, cron, task); return "任务添加成功"; } }
注意事项
- 任务ID建议由用户自定义或者系统生成全局唯一标识,避免任务冲突
- 如果需要执行固定间隔的周期任务,可以替换
CronTrigger为fixedRate/fixedDelay相关调度方法 - 核心线程数需要根据业务实际运行的最大任务数调整,避免线程不足导致任务阻塞
内容的提问来源于stack exchange,提问作者Ktess
相关产品推荐
相关产品推荐

