You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Spring Boot中使用ScheduledExecutorService实现每月1日执行定时任务

Spring Boot 基于 ScheduledExecutorService 实现每月1号定时任务方案

核心实现逻辑

ScheduledExecutorService 原生仅支持固定延迟、固定周期的调度能力,不直接支持月度级别的时间规则调度,因此我们采用「单次调度+动态计算下次执行时间」的逻辑实现需求:

  1. 每次提交单次调度任务,延迟时间为当前时间到下一个月1号0点的时间差
  2. 任务执行完成后,自动计算下一次执行的延迟时间,提交新的调度任务,循环往复
  3. 项目启动时自动触发第一次调度的提交

具体代码实现

1. 配置 ScheduledExecutorService 实例

首先在配置类中注册线程池Bean,避免每次手动创建线程池:

import com.google.common.util.concurrent.ThreadFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
@Configuration
public class SchedulerConfig {
    @Bean
    public ScheduledExecutorService scheduledExecutorService() {
        // 单线程足够处理月度低频任务,可根据实际业务调整核心线程数
        ThreadFactory threadFactory = new ThreadFactoryBuilder()
                .setNameFormat("monthly-task-thread-%d")
                .setDaemon(false)
                .build();
        return Executors.newScheduledThreadPool(1, threadFactory);
    }
}

若项目未引入Guava依赖,可自行实现ThreadFactory接口自定义线程创建规则。

2. 实现定时任务调度逻辑

import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@Service
public class MonthlyTaskService implements InitializingBean {
    @Resource
    private ScheduledExecutorService scheduledExecutorService;
    /**
     * 自定义业务执行逻辑,替换为你的实际业务代码
     */
    private void runBusinessTask() {
        System.out.println("每月1号定时任务执行,当前时间:" + LocalDateTime.now());
    }
    /**
     * 计算当前时间到下一个月1号0点的延迟毫秒数
     */
    private long calculateNextDelayMillis() {
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime nextExecuteTime = LocalDateTime.of(
                now.plusMonths(1).getYear(),
                now.plusMonths(1).getMonth(),
                1,
                0, 0, 0
        );
        return Duration.between(now, nextExecuteTime).toMillis();
    }
    /**
     * 提交单次调度任务
     */
    private void submitScheduleTask() {
        scheduledExecutorService.schedule(() -> {
            try {
                // 执行业务逻辑
                runBusinessTask();
            } catch (Exception e) {
                // 必须捕获所有异常,避免任务抛出异常后终止后续调度
                e.printStackTrace();
            } finally {
                // 任务执行完成后提交下一次调度
                submitScheduleTask();
            }
        }, calculateNextDelayMillis(), TimeUnit.MILLISECONDS);
    }
    /**
     * Spring Bean初始化完成后自动触发第一次调度提交
     */
    @Override
    public void afterPropertiesSet() {
        submitScheduleTask();
    }
}

注意事项

  • 必须在任务执行逻辑中捕获所有异常,未捕获的异常会导致当前调度任务终止,不会触发下一次调度提交
  • 多实例部署场景下需要引入分布式锁(如Redis锁),任务执行前先抢锁,抢锁成功再执行业务,避免多实例重复执行任务
  • 若需要支持当月1号已过的情况下启动服务补跑任务,可在首次提交调度前判断当前时间是否晚于当月1号0点,若晚于则先执行一次业务逻辑,再计算下一次调度延迟
  • 如果没有强制要求使用ScheduledExecutorService,Spring自带的@Scheduled注解直接配置cron表达式0 0 0 1 * ?即可实现每月1号0点执行,实现更简单

内容的提问来源于stack exchange,提问作者Amit Gairola

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.11 05:36:00