SpringBoot定时任务@Scheduled注解详解

SpringBoot定时任务@Scheduled注解详解SpringBoot 定时任务 Scheduled 注解详解项目开发中 经常会遇到定时任务的场景 Spring 提供了 Scheduled 注解 方便进行定时任务的开发概述要使用 Scheduled 注解 首先需要在启动类添加 EnableSchedu 启用 Spring 的计划任务执行功能 这样可以在容器中的任何 Spring 管理的 bean 上检测 Scheduled 注解 执行计划任务注解定义 Target ElementType METHOD ElementType ANNOTATION TYPE

SpringBoot定时任务@Scheduled注解详解

项目开发中,经常会遇到定时任务的场景,Spring提供了@Scheduled注解,方便进行定时任务的开发

概述

要使用@Scheduled注解,首先需要在启动类添加@EnableScheduling,启用Spring的计划任务执行功能,这样可以在容器中的任何Spring管理的bean上检测@Scheduled注解,执行计划任务

注解定义

@Target({ 
   ElementType.METHOD, ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Repeatable(Schedules.class) public @interface Scheduled { 
    String cron() default ""; String zone() default ""; long fixedDelay() default -1; String fixedDelayString() default ""; long fixedRate() default -1; String fixedRateString() default ""; long initialDelay() default -1; String initialDelayString() default ""; } 

参数说明

参数 参数说明 示例
cron 任务执行的cron表达式 0/1 * * * * ?
zone cron表达时解析使用的时区,默认为服务器的本地时区,使用java.util.TimeZone#getTimeZone(String)方法解析 GMT-8:00
fixedDelay 上一次任务执行结束到下一次执行开始的间隔时间,单位为ms 1000
fixedDelayString 上一次任务执行结束到下一次执行开始的间隔时间,使用java.time.Duration#parse解析 PT15M
fixedRate 以固定间隔执行任务,即上一次任务执行开始到下一次执行开始的间隔时间,单位为ms,若在调度任务执行时,上一次任务还未执行完毕,会加入worker队列,等待上一次执行完成后立即执行下一次任务 2000
fixedRateString 与fixedRate逻辑一致,只是使用java.time.Duration#parse解析 PT15M
initialDelay 首次任务执行的延迟时间 1000
initialDelayString 首次任务执行的延迟时间,使用java.time.Duration#parse解析 PT15M

源码解析

配置了@Scheduled注解的方法,Spring的处理是通过注册ScheduledAnnotationBeanPostProcessor来执行,将不同配置参数的任务分配给不同的handler处理,核心代码如下

  • org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor#postProcessAfterInitialization
@Override public Object postProcessAfterInitialization(Object bean, String beanName) { 
    if (bean instanceof AopInfrastructureBean || bean instanceof TaskScheduler || bean instanceof ScheduledExecutorService) { 
    // Ignore AOP infrastructure such as scoped proxies. return bean; } Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean); if (!this.nonAnnotatedClasses.contains(targetClass) && AnnotationUtils.isCandidateClass(targetClass, Arrays.asList(Scheduled.class, Schedules.class))) { 
    Map<Method, Set<Scheduled>> annotatedMethods = MethodIntrospector.selectMethods(targetClass, (MethodIntrospector.MetadataLookup<Set<Scheduled>>) method -> { 
    Set<Scheduled> scheduledMethods = AnnotatedElementUtils.getMergedRepeatableAnnotations( method, Scheduled.class, Schedules.class); return (!scheduledMethods.isEmpty() ? scheduledMethods : null); }); if (annotatedMethods.isEmpty()) { 
    this.nonAnnotatedClasses.add(targetClass); if (logger.isTraceEnabled()) { 
    logger.trace("No @Scheduled annotations found on bean class: " + targetClass); } } else { 
    // Non-empty set of methods annotatedMethods.forEach((method, scheduledMethods) -> scheduledMethods.forEach(scheduled -> processScheduled(scheduled, method, bean))); if (logger.isTraceEnabled()) { 
    logger.trace(annotatedMethods.size() + " @Scheduled methods processed on bean '" + beanName + "': " + annotatedMethods); } } } return bean; } 
  • org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor#processScheduled
/ * Process the given {@code @Scheduled} method declaration on the given bean. * @param scheduled the @Scheduled annotation * @param method the method that the annotation has been declared on * @param bean the target bean instance * @see #createRunnable(Object, Method) */ protected void processScheduled(Scheduled scheduled, Method method, Object bean) { 
    try { 
    Runnable runnable = createRunnable(bean, method); boolean processedSchedule = false; String errorMessage = "Exactly one of the 'cron', 'fixedDelay(String)', or 'fixedRate(String)' attributes is required"; Set<ScheduledTask> tasks = new LinkedHashSet<>(4); // Determine initial delay long initialDelay = scheduled.initialDelay(); String initialDelayString = scheduled.initialDelayString(); if (StringUtils.hasText(initialDelayString)) { 
    Assert.isTrue(initialDelay < 0, "Specify 'initialDelay' or 'initialDelayString', not both"); if (this.embeddedValueResolver != null) { 
    initialDelayString = this.embeddedValueResolver.resolveStringValue(initialDelayString); } if (StringUtils.hasLength(initialDelayString)) { 
    try { 
    initialDelay = parseDelayAsLong(initialDelayString); } catch (RuntimeException ex) { 
    throw new IllegalArgumentException( "Invalid initialDelayString value \"" + initialDelayString + "\" - cannot parse into long"); } } } // Check cron expression String cron = scheduled.cron(); if (StringUtils.hasText(cron)) { 
    String zone = scheduled.zone(); if (this.embeddedValueResolver != null) { 
    cron = this.embeddedValueResolver.resolveStringValue(cron); zone = this.embeddedValueResolver.resolveStringValue(zone); } if (StringUtils.hasLength(cron)) { 
    Assert.isTrue(initialDelay == -1, "'initialDelay' not supported for cron triggers"); processedSchedule = true; if (!Scheduled.CRON_DISABLED.equals(cron)) { 
    TimeZone timeZone; if (StringUtils.hasText(zone)) { 
    timeZone = StringUtils.parseTimeZoneString(zone); } else { 
    timeZone = TimeZone.getDefault(); } tasks.add(this.registrar.scheduleCronTask(new CronTask(runnable, new CronTrigger(cron, timeZone)))); } } } // At this point we don't need to differentiate between initial delay set or not anymore if (initialDelay < 0) { 
    initialDelay = 0; } // Check fixed delay long fixedDelay = scheduled.fixedDelay(); if (fixedDelay >= 0) { 
    Assert.isTrue(!processedSchedule, errorMessage); processedSchedule = true; tasks.add(this.registrar.scheduleFixedDelayTask(new FixedDelayTask(runnable, fixedDelay, initialDelay))); } String fixedDelayString = scheduled.fixedDelayString(); if (StringUtils.hasText(fixedDelayString)) { 
    if (this.embeddedValueResolver != null) { 
    fixedDelayString = this.embeddedValueResolver.resolveStringValue(fixedDelayString); } if (StringUtils.hasLength(fixedDelayString)) { 
    Assert.isTrue(!processedSchedule, errorMessage); processedSchedule = true; try { 
    fixedDelay = parseDelayAsLong(fixedDelayString); } catch (RuntimeException ex) { 
    throw new IllegalArgumentException( "Invalid fixedDelayString value \"" + fixedDelayString + "\" - cannot parse into long"); } tasks.add(this.registrar.scheduleFixedDelayTask(new FixedDelayTask(runnable, fixedDelay, initialDelay))); } } // Check fixed rate long fixedRate = scheduled.fixedRate(); if (fixedRate >= 0) { 
    Assert.isTrue(!processedSchedule, errorMessage); processedSchedule = true; tasks.add(this.registrar.scheduleFixedRateTask(new FixedRateTask(runnable, fixedRate, initialDelay))); } String fixedRateString = scheduled.fixedRateString(); if (StringUtils.hasText(fixedRateString)) { 
    if (this.embeddedValueResolver != null) { 
    fixedRateString = this.embeddedValueResolver.resolveStringValue(fixedRateString); } if (StringUtils.hasLength(fixedRateString)) { 
    Assert.isTrue(!processedSchedule, errorMessage); processedSchedule = true; try { 
    fixedRate = parseDelayAsLong(fixedRateString); } catch (RuntimeException ex) { 
    throw new IllegalArgumentException( "Invalid fixedRateString value \"" + fixedRateString + "\" - cannot parse into long"); } tasks.add(this.registrar.scheduleFixedRateTask(new FixedRateTask(runnable, fixedRate, initialDelay))); } } // Check whether we had any attribute set Assert.isTrue(processedSchedule, errorMessage); // Finally register the scheduled tasks synchronized (this.scheduledTasks) { 
    Set<ScheduledTask> regTasks = this.scheduledTasks.computeIfAbsent(bean, key -> new LinkedHashSet<>(4)); regTasks.addAll(tasks); } } catch (IllegalArgumentException ex) { 
    throw new IllegalStateException( "Encountered invalid @Scheduled method '" + method.getName() + "': " + ex.getMessage()); } } 
  • org.springframework.scheduling.config.ScheduledTaskRegistrar#scheduleTasks
/ * Schedule all registered tasks against the underlying * {@linkplain #setTaskScheduler(TaskScheduler) task scheduler}. */ proected void scheduleTasks() { 
    if (this.taskScheduler == null) { 
    this.localExecutor = Executors.newSingleThreadScheduledExecutor(); this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor); } if (this.triggerTasks != null) { 
    for (TriggerTask task : this.triggerTasks) { 
    addScheduledTask(scheduleTriggerTask(task)); } } if (this.cronTasks != null) { 
    for (CronTask task : this.cronTasks) { 
    addScheduledTask(scheduleCronTask(task)); } } if (this.fixedRateTasks != null) { 
    for (IntervalTask task : this.fixedRateTasks) { 
    addScheduledTask(scheduleFixedRateTask(task)); } } if (this.fixedDelayTasks != null) { 
    for (IntervalTask task : this.fixedDelayTasks) { 
    addScheduledTask(scheduleFixedDelayTask(task)); } } } 

使用详解

定时任务同步/异步执行

// org.springframework.scheduling.config.ScheduledTaskRegistrar#scheduleTasks if (this.taskScheduler == null) { 
    this.localExecutor = Executors.newSingleThreadScheduledExecutor(); this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor); } // java.util.concurrent.Executors#newSingleThreadScheduledExecutor() public static ScheduledExecutorService newSingleThreadScheduledExecutor() { 
    return new DelegatedScheduledExecutorService (new ScheduledThreadPoolExecutor(1)); } 

代码示例:

@Slf4j @Component public class RunIntervalTestScheduler { 
    @Scheduled(cron = "0/1 * * * * ?") public void singleThreadTest1() { 
    log.info("singleThreadTest1"); LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1)); } @Scheduled(cron = "0/1 * * * * ?") public void singleThreadTest2() { 
    log.info("singleThreadTest2"); LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1)); } @Scheduled(cron = "0/1 * * * * ?") public void singleThreadTest3() { 
    log.info("singleThreadTest3"); LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1)); } } 

可以看到,默认情况下,三个任务串行执行,都使用pool-1-thread-1同一个线程池,并且线程只有一个

可以通过实现SchedulingConfigurer接口,手动创建线程池,配置期望的线程数量

示例代码:

@Configuration public class ScheduledConfig implements SchedulingConfigurer { 
    / * 任务执行线程池大小 */ private static final int TASK_POOL_SIZE = 50; / * 线程名 */ private static final String TASK_THREAD_PREFIX = "test-task-"; @Override public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) { 
    ThreadPoolTaskScheduler taskPool = new ThreadPoolTaskScheduler(); taskPool.setPoolSize(TASK_POOL_SIZE); taskPool.setThreadNamePrefix(TASK_THREAD_PREFIX); taskPool.initialize(); scheduledTaskRegistrar.setTaskScheduler(taskPool); } } 

任务执行结果:

在这里插入图片描述

此时任务的执行已经异步化,从自定义线程池中分配线程执行任务,在实际应用中需要考虑实际任务数量,创建相应大小的线程池

fixedRate/fixedDelay区别

  • fixedRate是配置上一次任务执行开始到下一次执行开始的间隔时间,不会等待上一次任务执行完成就会调度下一次任务,将其放入等待队列中

代码示例:

@Slf4j @Component public class RunIntervalTestScheduler { 
    @Scheduled(initialDelay = 1000, fixedRate = 1000) public void fixedRate() throws Exception { 
    log.info("fixedRate run"); TimeUnit.SECONDS.sleep(3); } } 

执行结果:

在这里插入图片描述

任务配置的fixedRate为1s,执行日志打印的时间间隔都是3s左右,也就是上一次执行完成后,紧接着就执行下一次任务

  • fixedDelay是配置的上一次任务执行结束到下一次执行开始的间隔时间,也就是说会等待上一次任务执行结束后,延迟间隔时间,再执行下一次任务

代码示例:

@Slf4j @Component public class RunIntervalTestScheduler { 
    @Scheduled(initialDelay = 1000, fixedDelay = 1000) public void fixedDelay() throws Exception { 
    log.info("fixedDelay run"); TimeUnit.SECONDS.sleep(3); } } 

执行结果:

在这里插入图片描述

任务配置的fixedDelay为1s,执行日志打印的时间间隔都是4s左右,也就是上一次执行完成后,延迟1s后执行下一次任务

  • cron表达式如果配置为类似每秒执行、每分钟执行(例:0/1 * * * * ?, 每秒执行),调度跟fixedDelay是一致的,也是在上一次任务执行结束后,等待间隔时间

代码示例:

@Slf4j @Component public class RunIntervalTestScheduler { 
    @Scheduled(cron = "0/1 * * * * ?") public void cronRun() throws Exception{ 
    log.info("cron run"); TimeUnit.SECONDS.sleep(3); } } 

执行结果:

在这里插入图片描述

执行日志打印的时间间隔都是4s左右,也就是上一次执行完成后,延迟1s后执行下一次任务

  • cron表达式如果配置为固定时间执行(例:1 * * * * ?, 秒数为1时执行),若上一次任务没有执行完,则不会调度本次任务,跳过本次执行,等待下一次执行周期

代码示例:

@Slf4j @Component public class RunIntervalTestScheduler { 
    @Scheduled(cron = "1 * * * * ?") public void cronRun() throws Exception{ 
    log.info("cron run"); TimeUnit.SECONDS.sleep(70); } } 

执行结果:

在这里插入图片描述

上一次任务未执行完毕,则跳过了本次执行

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/212038.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月18日 下午8:59
下一篇 2026年3月18日 下午8:59


相关推荐

  • 腾讯元宝电脑安装卸载不了

    腾讯元宝电脑安装卸载不了

    2026年3月13日
    1
  • 操作系统 文件管理实验报告

    操作系统 文件管理实验报告实验要求实验目的与要求用高级语言编写和调试一个简单的文件系统 模拟文件管理的工作过程 从而对各种文件操作命令的实质内容和执行过程有比较深入的了解 要求设计一个 n 个用户的文件系统 每次用户可保存 m 个文件 用户在一次运行中只能打开一个文件 对文件必须设置保护措施 且至少有 Create delete open close read write 等命令 二 例题 设计一个 10 个用户的文件系统 每次用户可保存 10 个文件 一次运行用户可以打开 5 个文件 程序采用二级文件目录 即设置主目录 MFD 和用户文

    2026年2月4日
    2
  • 打造一个红旗(redflag)Linux的基础镜像(base image)「建议收藏」

    打造一个红旗(redflag)Linux的基础镜像(base image)「建议收藏」起因由于工作原因,想找一个红旗Linux的基础镜像(baseimage)。网上搜了一下,但没有现成的。起初是想找对应的centos版本来代替,但又怕有些莫名其妙的问题。官方文档不过搜索过程中,

    2022年7月2日
    44
  • PyCharm画图时不能显示中文,只有框框,何解?

    PyCharm画图时不能显示中文,只有框框,何解?初学 Python 的小白 画图时遇到问题 图中不能显示中文标题和坐标轴标题 只有矩形小框框 没有显示文字添加以下两行代码即可 plt rcParams font sans serif SimHei 显示中文 plt rcParams axes unicode minus False 正常显示负号请注意这两句应该放在 import 语句之后 至少应该在 importmatplo pyplotasplt 这句代码之后 另外注意 as 后面是 plt 还是 plo

    2026年3月17日
    2
  • DSP优化——C6000[通俗易懂]

    DSP优化——C6000[通俗易懂]第一章C6000系列DSP的体系结构简介TI的C6000系列DSP内部采用的哈佛结构的体系结构,其数据段和代码段是分开存放的并且独立编址,减轻了程序在运行时的访问存储数据的瓶颈。其中C62和C64系列均为定点的DSP处理器,而C67系列为浮点的DSP,本文主要介绍C64系列的优化方法。C6455的功能模块图如下:图1:c6455的功能方框图C64系列DSP有两个数据通道,如下

    2022年6月16日
    126
  • 月之暗面发布Kimi Claw:一个在云端拥有40G空间的24×7运行的OpenClaw,基于Kimi模型驱动

    月之暗面发布Kimi Claw:一个在云端拥有40G空间的24×7运行的OpenClaw,基于Kimi模型驱动

    2026年3月12日
    2

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号