Spring中如何切换定时任务?实现单类双Cron任务互斥运行
实现同一时刻仅运行单个定时任务的方案
针对你提出的需求——在同一个@EnableScheduling标注的类中,两个定时任务同一时刻仅运行一个,新任务触发时停止正在运行的旧任务——可以通过跟踪任务执行状态+主动中断旧任务的方式实现,下面是具体的代码示例和详细解释:
核心实现思路
- 用
AtomicReference<ScheduledFuture<?>>跟踪当前正在执行的任务实例,方便后续取消操作; - 用可重入锁
ReentrantLock保证任务切换逻辑的线程安全,避免并发修改任务状态; - 统一封装任务执行入口,在新任务启动前先取消旧任务,再执行新任务;
- 在业务逻辑中处理中断异常,确保任务能被及时停止。
完整代码示例
@Service @EnableScheduling public class RecognizeService { // 原子引用,用于跟踪当前正在执行的任务 private final AtomicReference<ScheduledFuture<?>> currentRunningTask = new AtomicReference<>(); // 线程安全锁,确保任务切换逻辑的原子性 private final ReentrantLock taskLock = new ReentrantLock(); // 注入Spring的任务调度器,用于提交和管理任务 @Autowired private TaskScheduler taskScheduler; @Scheduled(cron = "0/4 * * * * ?") public void recognize1() throws IOException, ParseException { executeBusinessTask(this::executeRecognize1Logic); } @Scheduled(cron = "0/4 * * * * ?") public void recognize2() throws IOException, ParseException { executeBusinessTask(this::executeRecognize2Logic); } /** * 统一任务执行入口,处理旧任务取消和新任务启动 */ private void executeBusinessTask(Runnable businessLogic) { taskLock.lock(); try { // 取消当前正在运行的任务(如果存在且未完成) ScheduledFuture<?> existingTask = currentRunningTask.get(); if (existingTask != null && !existingTask.isDone()) { // true表示中断正在运行的任务线程 boolean isCancelled = existingTask.cancel(true); if (isCancelled) { System.out.println("已中断正在运行的旧任务"); } } // 提交新任务并更新当前任务引用 ScheduledFuture<?> newTask = taskScheduler.schedule(businessLogic, new Date()); currentRunningTask.set(newTask); } finally { // 确保锁始终被释放,避免死锁 taskLock.unlock(); } } /** * 原recognize1的业务逻辑抽离 */ private void executeRecognize1Logic() { try { System.out.println("开始执行recognize1,时间:" + new Date()); // 模拟业务执行(比如耗时操作) Thread.sleep(3000); System.out.println("recognize1执行完成,时间:" + new Date()); } catch (InterruptedException e) { // 处理任务中断,比如清理资源、恢复线程中断状态 System.out.println("recognize1被中断,停止执行"); Thread.currentThread().interrupt(); } } /** * 原recognize2的业务逻辑抽离 */ private void executeRecognize2Logic() { try { System.out.println("开始执行recognize2,时间:" + new Date()); // 模拟业务执行 Thread.sleep(3000); System.out.println("recognize2执行完成,时间:" + new Date()); } catch (InterruptedException e) { System.out.println("recognize2被中断,停止执行"); Thread.currentThread().interrupt(); } } }
关键细节说明
- 任务中断处理:调用
cancel(true)会触发任务线程的InterruptedException,所以业务逻辑中必须捕获该异常并处理,否则任务可能无法及时停止。同时要恢复线程的中断状态(Thread.currentThread().interrupt()),避免影响后续操作。 - 线程安全保障:
ReentrantLock确保在多任务同时触发时,任务切换逻辑(取消旧任务+启动新任务)是原子操作,不会出现并发修改currentRunningTask的问题。 - TaskScheduler注入:Spring Boot会自动配置
TaskScheduler实例,无需额外配置即可直接注入使用。
注意事项
如果你的业务逻辑中涉及IO操作或阻塞调用,需要确保这些操作能响应线程中断,否则即使调用了cancel(true),任务可能仍无法及时停止。比如对于某些不响应中断的阻塞操作,可能需要额外的停止逻辑(比如关闭IO流、设置停止标记等)。
内容的提问来源于stack exchange,提问作者Every Thing
相关产品推荐
相关产品推荐

