Spring Boot定时任务原理

news2025/2/24 1:32:44

Spring Boot定时任务原理

在现代应用中,定时任务的调度是实现周期性操作的关键机制。Spring Boot 提供了强大的定时任务支持,通过注解驱动的方式,开发者可以轻松地为方法添加定时任务功能。本文将深入探讨 Spring Boot 中定时任务的实现原理,重点分析 @EnableSchedulingScheduledAnnotationBeanPostProcessor 的作用,以及任务如何被注册和执行。我们还将详细介绍底层使用的线程池调度器 ThreadPoolTaskScheduler 和 Java 内置的 ScheduledThreadPoolExecutor,它们如何协同工作,保证定时任务的准确执行。此外,我们还将探讨任务调度的线程阻塞与唤醒机制,深入剖析延迟队列(DelayedWorkQueue)如何有效管理任务的执行顺序。通过本文的学习,你将能够更好地理解和应用 Spring Boot 定时任务,提升应用的调度能力和性能。

1.注解驱动

Spring Boot通过@EnableScheduling激活定时任务支持,而EnableScheduling注解导入了SchedulingConfiguration,这个类创建了一个名为ScheduledAnnotationBeanPostProcessorbean,而这个bean就是定时任务的关键

/**
 * {@code @Configuration} class that registers a {@link ScheduledAnnotationBeanPostProcessor}
 * bean capable of processing Spring's @{@link Scheduled} annotation.
 *
 * <p>This configuration class is automatically imported when using the
 * {@link EnableScheduling @EnableScheduling} annotation. See
 * {@code @EnableScheduling}'s javadoc for complete usage details.
 *
 * @author Chris Beams
 * @since 3.1
 * @see EnableScheduling
 * @see ScheduledAnnotationBeanPostProcessor
 */
@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class SchedulingConfiguration {

	@Bean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public ScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor() {
		return new ScheduledAnnotationBeanPostProcessor();
	}

}

2.对ScheduledAnnotationBeanPostProcessor的分析

1. 类职责

  • 核心作用:扫描 Spring Bean 中的 @Scheduled 注解方法,将其转换为定时任务,并注册到任务调度器。

2. 定时任务注册的关键流程

代码都是经过简化的代码,实际上我去看Spring的源码,发现代码都很长,但是整体意思是差不多的

Bean 初始化后扫描注解(关键方法:postProcessAfterInitialization
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
    // 1. 跳过 AOP 基础设施类
   if (bean instanceof AopInfrastructureBean || bean instanceof TaskScheduler ||
				bean instanceof ScheduledExecutorService) {
			// Ignore AOP infrastructure such as scoped proxies.
			return bean;
		}


    // 2. 检查类是否包含 @Scheduled 注解
    Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean);
    if (!nonAnnotatedClasses.contains(targetClass) && 
        AnnotationUtils.isCandidateClass(targetClass, List.of(Scheduled.class, Schedules.class))) {

        // 3. 反射查找所有带 @Scheduled 的方法
        Map<Method, Set<Scheduled>> annotatedMethods = MethodIntrospector.selectMethods(targetClass, 
            method -> AnnotatedElementUtils.getMergedRepeatableAnnotations(method, Scheduled.class, Schedules.class));

        // 4. 处理每个带注解的方法
        annotatedMethods.forEach((method, scheduledAnnotations) -> 
            scheduledAnnotations.forEach(scheduled -> processScheduled(scheduled, method, bean)));
    }
    return bean;
}
  • 跳过无关 Bean:如 AOP 代理类、TaskScheduler 本身。
  • 反射扫描方法:通过 MethodIntrospector 查找所有带有 @Scheduled 的方法。
  • 注解聚合:支持 @Schedules 多注解合并。
解析任务参数并注册(关键方法:processScheduled
protected void processScheduled(Scheduled scheduled, Method method, Object bean) {
    // 1. 创建 Runnable 任务
    Runnable runnable = createRunnable(bean, method);

    // 2. 解析时间参数(cron/fixedDelay/fixedRate)
    if (StringUtils.hasText(cron)) {
        // 处理 cron 表达式
        CronTask task = new CronTask(runnable, new CronTrigger(cron, timeZone));
        tasks.add(registrar.scheduleCronTask(task));
    } else if (fixedDelay > 0) {
        // 处理 fixedDelay
        FixedDelayTask task = new FixedDelayTask(runnable, fixedDelay, initialDelay);
        tasks.add(registrar.scheduleFixedDelayTask(task));
    } else if (fixedRate > 0) {
        // 处理 fixedRate
        FixedRateTask task = new FixedRateTask(runnable, fixedRate, initialDelay);
        tasks.add(registrar.scheduleFixedRateTask(task));
    }

    // 3. 注册任务到 ScheduledTaskRegistrar
    synchronized (scheduledTasks) {
        scheduledTasks.computeIfAbsent(bean, key -> new LinkedHashSet<>()).addAll(tasks);
    }
}
  • 任务封装:将方法封装为 ScheduledMethodRunnable
  • 时间参数解析:
    • 支持 cronfixedDelayfixedRate 三种模式。
    • 处理 initialDelay 初始延迟。
    • 使用 embeddedValueResolver 解析占位符(如 ${task.interval})。
  • 任务注册:最终任务被添加到 ScheduledTaskRegistrar
启动任务调度(关键方法:finishRegistration
private void finishRegistration() {
    // 1. 配置 TaskScheduler(优先级:显式设置 > 查找 Bean > 默认单线程)
    if (registrar.getScheduler() == null) {
        TaskScheduler scheduler = resolveSchedulerBean(beanFactory, TaskScheduler.class, false);
        registrar.setTaskScheduler(scheduler);
    }

    // 2. 调用 SchedulingConfigurer 自定义配置(扩展点)
    List<SchedulingConfigurer> configurers = beanFactory.getBeansOfType(SchedulingConfigurer.class);
    configurers.forEach(configurer -> configurer.configureTasks(registrar));

    // 3. 启动所有注册的任务
    registrar.afterPropertiesSet();
}
  • 调度器解析:
    • 默认查找名为 taskScheduler 的 Bean。
    • 若无则创建单线程调度器(Executors.newSingleThreadScheduledExecutor())。
  • 扩展点:允许通过 SchedulingConfigurer 自定义任务注册逻辑。
  • 最终启动:调用 afterPropertiesSet() 触发任务调度。

3.ThreadPoolTaskScheduler的剖析

ThreadPoolTaskScheduler 是 Spring 对 Java ScheduledThreadPoolExecutor 的封装,是 @Scheduled 定时任务的底层执行引擎。

  • 继承关系:继承 ExecutorConfigurationSupport,实现 TaskScheduler 接口,整合了线程池管理与定时任务调度。
  • 底层依赖:基于 ScheduledThreadPoolExecutor,支持 周期性任务(fixedRate/fixedDelay)和 动态触发任务(如 cron 表达式)。

线程池初始化(关键方法:initializeExecutor

同样,这里和以后的部分也都是伪代码

@Override
protected ExecutorService initializeExecutor(
        ThreadFactory threadFactory, RejectedExecutionHandler rejectedExecutionHandler) {

    // 创建 ScheduledThreadPoolExecutor
    this.scheduledExecutor = createExecutor(this.poolSize, threadFactory, rejectedExecutionHandler);

    // 配置线程池策略(如取消后立即移除任务)
    if (this.scheduledExecutor instanceof ScheduledThreadPoolExecutor scheduledPoolExecutor) {
        scheduledPoolExecutor.setRemoveOnCancelPolicy(this.removeOnCancelPolicy);
        // 其他策略设置...
    }
    return this.scheduledExecutor;
}

这部分是我复制源码的,可以清晰的看到,底层就是new了ScheduledThreadPoolExecutor

	protected ScheduledExecutorService createExecutor(
			int poolSize, ThreadFactory threadFactory, RejectedExecutionHandler rejectedExecutionHandler) {

		return new ScheduledThreadPoolExecutor(poolSize, threadFactory, rejectedExecutionHandler);
	}

4.ScheduledThreadPoolExecutor的原理分析

核心成员:

  • 任务队列:使用 DelayedWorkQueue(内部实现的小顶堆),按任务执行时间排序。
  • 线程池:复用 ThreadPoolExecutor 的线程管理机制,支持核心线程数和最大线程数配置。

2. 定时任务调度机制

所有定时任务被封装为 ScheduledFutureTask 对象,其核心逻辑如下:

private class ScheduledFutureTask<V> extends FutureTask<V> implements RunnableScheduledFuture<V> {
    private long time;          // 下一次执行时间(纳秒)
    private final long period;  // 周期(正数:fixedRate;负数:fixedDelay)
    private int heapIndex;      // 在 DelayedWorkQueue 中的索引

    public void run() {
        if (isPeriodic()) {
            // 周期性任务:重新计算下一次执行时间,并重新加入队列
            setNextRunTime();
            reExecutePeriodic(outerTask);
        } else {
            // 一次性任务:直接执行
            super.run();
        }
    }
}
  1. 任务提交:通过 schedulescheduleAtFixedRate 等方法提交任务。
  2. 队列管理:任务被封装为 ScheduledFutureTask 并加入 DelayedWorkQueue
  3. 线程唤醒:工作线程 (Worker) 从队列获取任务,若任务未到执行时间,线程进入限时等待(available.awaitNanos(delay))。
  4. 任务执行:到达执行时间后,线程执行任务:
    • 固定速率(fixedRate):执行完成后,根据 period 计算下一次执行时间(time += period)。
    • 固定延迟(fixedDelay):执行完成后,根据当前时间计算下一次执行时间(time = now() + (-period))。
  5. 重新入队:周期性任务执行后,重新加入队列等待下次调度。

3.DelayedWorkQueue的简单剖析

DelayQueue队列是一个延迟队列,DelayQueue中存放的元素必须实现Delayed接口的元素,实现接口后相当于是每个元素都有个过期时间,当队列进行take获取元素时,先要判断元素有没有过期,只有过期的元素才能出队操作,没有过期的队列需要等待剩余过期时间才能进行出队操作。

DelayQueue队列内部使用了PriorityQueue优先队列来进行存放数据,它采用的是二叉堆进行的优先队列,使用ReentrantLock锁来控制线程同步,由于内部元素是采用的PriorityQueue来进行存放数据,所以Delayed接口实现了Comparable接口,用于比较来控制优先级

线程阻塞与唤醒逻辑
(1) 取任务时的阻塞(take() 方法)

当线程调用 take() 方法从队列中获取任务时,若队列为空或队头任务未到期,线程会进入阻塞状态:

public E take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        for (;;) {
            E first = q.peek();
            if (first == null) {
                available.await(); // 队列为空时无限等待
            } else {
                long delay = first.getDelay(NANOSECONDS);
                if (delay <= 0) return q.poll(); // 任务已到期,取出执行
                if (leader != null) {
                    available.await(); // 其他线程已为队头任务等待,本线程无限等待
                } else {
                    Thread thisThread = Thread.currentThread();
                    leader = thisThread; // 标记当前线程为“领导者”
                    try {
                        available.awaitNanos(delay); // 限时等待到期时间
                    } finally {
                        if (leader == thisThread) leader = null;
                    }
                }
            }
        }
    } finally {
        if (leader == null && q.peek() != null) available.signal();
        lock.unlock();
    }
}
  • 关键逻辑:
    • leader 线程优化:避免多个线程同时等待同一任务到期,仅一个线程(leader)限时等待,其他线程无限等待
    • 限时等待:通过 available.awaitNanos(delay) 阻塞到任务到期时间。
(2) 插入新任务时的唤醒(offer() 方法)

当新任务被插入队列时,若新任务成为队头(即最早到期),会触发唤醒逻辑:

public boolean offer(E e) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        q.offer(e); // 插入任务并调整堆结构
        if (q.peek() == e) { // 新任务成为队头
            leader = null;
            available.signal(); // 唤醒等待线程
        }
        return true;
    } finally {
        lock.unlock();
    }
}
  • 唤醒条件:
    • 插入的任务成为新的队头(即其到期时间最早)。
    • 调用available.signal()唤醒等待的线程(leader)或其他线程

(3) 唤醒机制总结
  • 何时唤醒:
    1. 超时唤醒:等待线程因任务到期而被 JVM 自动唤醒。
    2. 插入新任务唤醒:新任务的到期时间早于当前队头任务时,插入线程会触发唤醒。
  • 唤醒对象:
    • 若存在 leader 线程(正在限时等待队头任务),优先唤醒它。
    • 若无 leader,唤醒任意一个等待线程

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2304157.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Unity 聊天气泡根据文本内容适配

第一步 拼接UI 1、对气泡图进行九宫图切割 2、设置底图pivot位置和对齐方式 pivot位置&#xff1a;&#xff08;0&#xff0c;1&#xff09; 对齐方式&#xff1a;左上对齐 3、设置文本pivot位置和对齐方式&#xff0c;并挂上布局组件 pivot设置和对齐方式和底图一样&#…

Android Studio安装配置及运行

一、下载Android Studio 官网下载&#xff1a;下载 Android Studio 和应用工具 - Android 开发者 | Android Developers 跳转到下载界面&#xff0c;选择同意条款&#xff0c;并点击下载&#xff0c;如图&#xff1a; 二、详细安装 双击下载的文件 三、配置Android Studio …

FTP 实验(ENSP模拟器实现)

FTP 概述 FTP&#xff08;File Transfer Protocol&#xff0c;文件传输协议&#xff09;是一种用于在网络上进行文件传输的标准协议。它允许用户在两台计算机之间上传和下载文件。 1、FTP采用客户端-服务器模型&#xff0c;客户端通过FTP客户端软件&#xff0c;连接到FTP服务…

基于 DeepSeek + Gemeni 打造 AI+前端的多人聊天室

开源项目 botgroup.chat 介绍 AI 多人聊天室&#xff1a; 一个基于 React 和 Cloudflare Pages(免费一键部署) 的多人 AI 聊天应用&#xff0c;支持多个 AI 角色同时参与对话&#xff0c;提供类似群聊的交互体验。体验地址&#xff1a;https://botgroup.chat 开源仓库&#x…

【电机控制器】ESP32-C3语言模型——豆包

【电机控制器】ESP32-C3语言模型——豆包 文章目录 [TOC](文章目录) 前言一、简介二、代码三、实验结果四、参考资料总结 前言 使用工具&#xff1a; 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、简介 二、代码 #include <WiFi.h> #inc…

Flask实现高效日志记录模块

目录 一. 简介&#xff1a; 1. 为什么需要请求日志 二. 日志模块组成 1. 对应日志表创建&#xff08;包含日志记录的关键字段&#xff09; 2. 编写日志记录静态方法 3. 在Flask中捕获请求日志 4. 捕获异常并记录错误日志 5. 编写日志接口数据展示 6. 写入数据展…

基于python深度学习遥感影像地物分类与目标识别、分割

我国高分辨率对地观测系统重大专项已全面启动&#xff0c;高空间、高光谱、高时间分辨率和宽地面覆盖于一体的全球天空地一体化立体对地观测网逐步形成&#xff0c;将成为保障国家安全的基础性和战略性资源。未来10年全球每天获取的观测数据将超过10PB&#xff0c;遥感大数据时…

Spring有哪些缺点?

大家好&#xff0c;我是锋哥。今天分享关于【Spring有哪些缺点?】面试题。希望对大家有帮助&#xff1b; Spring有哪些缺点? 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 Spring框架是一个广泛使用的企业级Java开发框架&#xff0c;提供了丰富的功能和强大的灵…

linux学习【7】Sourc Insight 4.0设置+操作

目录 1.Source Insight是什么&#xff1f;2.需要哪些配置&#xff1f;3.怎么新建项目4.一些问题的解决1.中文乱码问题 5.常规使用1. 在工程中打开文件2. 在文件中查看函数或变量的定义3. 查找函数或变量的引用4. 快捷键 按照这个设置就可以了&#xff0c;下面的设置会标明设置理…

PHP2(WEB)

##解题思路 打开页面什么线索都没有&#xff0c;目录扫描只是扫出来一个index.php&#xff0c;而源代码没有东西&#xff0c;且/robots.txt是不允许访问的 于是一番查询后发现&#xff0c;有个index.phps的文件路径&#xff0c;里头写着一段php的逻辑&#xff0c;对url的id参数…

Halcon 3D加快表面匹配速度

文章目录 gen_box_object_model_3d 创建一个代表盒子的 3D 物体模型write_surface_model — 将表面模型写入文件read_surface_model — 将表面模型读取prepare_object_model_3d - 为某个操作准备三维对象模型select_points_object_model_3d - 对 3D 物体模型的属性应用阈值。se…

Ollama 本地GUI客户端:为DeepSeek用户量身定制的智能模型管理与交互工具

Ollama 本地GUI客户端&#xff1a;为DeepSeek用户量身定制的智能模型管理与交互工具 相关资源文件已经打包成EXE文件&#xff0c;可双击直接运行程序&#xff0c;且文章末尾已附上相关源码&#xff0c;以供大家学习交流&#xff0c;博主主页还有更多Python相关程序案例&#xf…

基于SSM的《计算机网络》题库管理系统(源码+lw+部署文档+讲解),源码可白嫖!

摘 要 《计算机网络》题库管理系统是一种新颖的考试管理模式&#xff0c;因为系统是用Java技术进行开发。系统分为三个用户进行登录并操作&#xff0c;分别是管理员、教师和学生。教师在系统后台新增试题和试卷&#xff0c;学生进行在线考试&#xff0c;还能对考生记录、错题…

对Revit事务机制的一些推测

什么是事务机制 首先&#xff0c;什么是事务机制。软件事务机制是指一种在软件系统中用于管理一系列操作的方法&#xff0c;这些操作要么全部成功完成&#xff0c;要么全部失败&#xff0c;不会出现部分完成的情况。事务机制确保了数据的一致性和完整性&#xff0c;特别是在并…

《微软量子芯片:开启量子计算新纪元》:此文为AI自动生成

量子计算的神秘面纱 在科技飞速发展的今天,量子计算作为前沿领域,正逐渐走进大众的视野。它宛如一把神秘的钥匙,有望开启未来科技变革的大门,而微软量子芯片则是这把钥匙上一颗璀璨的明珠。 量子计算,简单来说,是一种遵循量子力学规律调控量子信息单元进行计算的新型计算…

SpringBoot+Vue+Mysql苍穹外卖

一.项目介绍 1.项目内容 苍穹外卖是一款为大学学子设计的校园外卖服务软件&#xff0c;旨在提供便捷的食堂外卖送至宿舍的服务。该软件包含系统管理后台和用户端&#xff08;微信小程序&#xff09;两部分&#xff0c;支持在线浏览菜品、添加购物车、下单等功能&#xff0c;并…

网络运维学习笔记 018 HCIA-Datacom综合实验02

文章目录 综合实验2sw3&#xff1a;sw4&#xff1a;gw&#xff1a;core1&#xff08;sw1&#xff09;&#xff1a;core2&#xff08;sw2&#xff09;&#xff1a;ISP 综合实验2 sw3&#xff1a; vlan 2 stp mode stp int e0/0/1 port link-type trunk port trunk allow-pass v…

QT 引入Quazip和Zlib源码工程到项目中,无需编译成库,跨平台,压缩进度

前言 最近在做项目时遇到一个需求&#xff0c;需要将升级的文件压缩成zip&#xff0c;再进行传输&#xff1b; 通过网络调研&#xff0c;有许多方式可以实现&#xff0c;例如QT私有模块的ZipReader、QZipWriter&#xff1b;或者第三方库zlib或者libzip或者quazip等&#xff1…

车载诊断架构 --- LIN节点路由转发注意事项

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 简单,单纯,喜欢独处,独来独往,不易合同频过着接地气的生活,除了生存温饱问题之外,没有什么过多的欲望,表面看起来很高冷,内心热情,如果你身…

Eclipse2024中文汉化教程(图文版)

对应Eclipse,部分人需要中文汉化,本章教程,介绍如何对Eclipse进行汉化的具体步骤。 一、汉化前的Eclipse 默认安装Eclipse的时候,默认一般都是English的,我当前版本是使用的是2024-06版本的Eclipse。 二、汉化详细步骤 点击上方菜单选项卡,Hep——Install New Software……