Spring Boot定时任务原理
Spring Boot定时任务原理
在现代应用中,定时任务的调度是实现周期性操作的关键机制。Spring Boot 提供了强大的定时任务支持,通过注解驱动的方式,开发者可以轻松地为方法添加定时任务功能。本文将深入探讨 Spring Boot 中定时任务的实现原理,重点分析 @EnableScheduling 和 ScheduledAnnotationBeanPostProcessor 的作用,以及任务如何被注册和执行。我们还将详细介绍底层使用的线程池调度器 ThreadPoolTaskScheduler 和 Java 内置的 ScheduledThreadPoolExecutor,它们如何协同工作,保证定时任务的准确执行。此外,我们还将探讨任务调度的线程阻塞与唤醒机制,深入剖析延迟队列(DelayedWorkQueue)如何有效管理任务的执行顺序。通过本文的学习,你将能够更好地理解和应用 Spring Boot 定时任务,提升应用的调度能力和性能。
1.注解驱动
Spring Boot通过@EnableScheduling激活定时任务支持,而EnableScheduling注解导入了SchedulingConfiguration,这个类创建了一个名为ScheduledAnnotationBeanPostProcessor 的bean,而这个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) {// 处理 fixedDelayFixedDelayTask task = new FixedDelayTask(runnable, fixedDelay, initialDelay);tasks.add(registrar.scheduleFixedDelayTask(task));} else if (fixedRate > 0) {// 处理 fixedRateFixedRateTask task = new FixedRateTask(runnable, fixedRate, initialDelay);tasks.add(registrar.scheduleFixedRateTask(task));}// 3. 注册任务到 ScheduledTaskRegistrarsynchronized (scheduledTasks) {scheduledTasks.computeIfAbsent(bean, key -> new LinkedHashSet<>()).addAll(tasks);}
}
- 任务封装:将方法封装为
ScheduledMethodRunnable。 - 时间参数解析:
- 支持
cron、fixedDelay、fixedRate三种模式。 - 处理
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) {// 创建 ScheduledThreadPoolExecutorthis.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();}}
}
- 任务提交:通过
schedule、scheduleAtFixedRate等方法提交任务。 - 队列管理:任务被封装为
ScheduledFutureTask并加入DelayedWorkQueue。 - 线程唤醒:工作线程 (
Worker) 从队列获取任务,若任务未到执行时间,线程进入限时等待(available.awaitNanos(delay))。 - 任务执行:到达执行时间后,线程执行任务:
- 固定速率(fixedRate):执行完成后,根据
period计算下一次执行时间(time += period)。 - 固定延迟(fixedDelay):执行完成后,根据当前时间计算下一次执行时间(
time = now() + (-period))。
- 固定速率(fixedRate):执行完成后,根据
- 重新入队:周期性任务执行后,重新加入队列等待下次调度。
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) 唤醒机制总结
- 何时唤醒:
- 超时唤醒:等待线程因任务到期而被 JVM 自动唤醒。
- 插入新任务唤醒:新任务的到期时间早于当前队头任务时,插入线程会触发唤醒。
- 唤醒对象:
- 若存在
leader线程(正在限时等待队头任务),优先唤醒它。 - 若无
leader,唤醒任意一个等待线程
- 若存在
相关文章:
Spring Boot定时任务原理
Spring Boot定时任务原理 在现代应用中,定时任务的调度是实现周期性操作的关键机制。Spring Boot 提供了强大的定时任务支持,通过注解驱动的方式,开发者可以轻松地为方法添加定时任务功能。本文将深入探讨 Spring Boot 中定时任务的实现原理…...
C#初级教程(7)——初级期末检测
练习 1:计算圆的周长和面积 改编题目:编写一个 C# 程序,让用户输入圆的半径,然后计算并输出该圆的周长和面积,结果保留两位小数。 using System;class CircleCalculation {static void Main(){const double pi 3.14…...
原生稀疏注意力机制(NSA):硬件对齐且可原生训练的稀疏注意力机制-论文阅读
摘要 长上下文建模对于下一代语言模型至关重要,但标准注意力机制的高计算成本带来了巨大的计算挑战。稀疏注意力提供了一种在保持模型能力的同时提高效率的有前途的方向。本文提出了一种名为 NSA(原生可训练稀疏注意力机制) 的方法ÿ…...
Apache Struts RCE (CVE-2024-53677)
前言 对目前的Apache Struts RCE (CVE-2024-53677)的poc进行总结,由于只能单个ip验证,所以自己更改一下代码,实现:多线程读取url验证并保存,更改为中文解释 免责声明 请勿利用文章内的相关技术从事非法测试…...
GIS地图、轨道交通与智能驾驶UI设计:未来交通的智能化探索
随着科技的飞速发展,我们正迎来一个高度智能化的未来。在这个时代背景下,GIS(地理信息系统)、轨道交通以及智能驾驶UI设计正逐步成为推动交通行业变革的重要力量。本文将深入探讨这三者之间的内在联系及其在未来交通系统中的应用前…...
OpenResty
文章目录 OpenResty执行原理getting-started 核心模块: lua-nginx-module (ngx_lua)常用指令配置指令的执行顺序 API OpenResty 官方文档: http://openresty.org/ 官方文档完全不明所以, 除了getting-started完全不知道下一步该干啥 (都不知道ngx是什么它就开始用了), 找不到架…...
如何将公钥正确添加到服务器的 authorized_keys 文件中以实现免密码 SSH 登录
1. 下载密钥文件 2. RSA 解析 将 id_ed25519 类型的私钥转换为 RSA 类型,要将 ED25519 私钥转换为 RSA 私钥,需要重新生成一个新的 RSA 密钥对。 步骤: 生成新的 RSA 密钥对 使用 ssh-keygen 来生成一个新的 RSA 密钥对。比如,执…...
SQLMesh 系列教程7- 详解 seed 模型
SQLMesh 是一个强大的数据建模和管道管理工具,允许用户通过 SQL 语句定义数据模型并进行版本控制。Seed 模型是 SQLMesh 中的一种特殊模型,主要用于初始化和填充基础数据集。它通常包含静态数据,如参考数据和配置数据,旨在为后续的…...
Git常见命令--助力开发
git常见命令: 创建初始化仓库: git 将文件提交到暂存区 git add 文件名 将文件提交到工作区 git commit -m "注释(例如这是发行的版本1)" 文件名 查看状态 如果暂存区没有文件被提交显示: $ git status On…...
学习整理安装php的uuid扩展以及uuid调用方法
学习整理安装php的uuid扩展以及uuid调用方法 1、安装uuid依赖库2、下载并安装3、ini中添加扩展4、re2c版本报错5、uuid调用方法 1、安装uuid依赖库 yum -y install uuid uuid-devel e2fsprogs-devel libuuid-devel2、下载并安装 点我下载uuid安装包 wget http://pecl.php.ne…...
算法系列之贪心算法
在算法中,贪心算法(Greedy Algorithm)是一种常见的解决优化问题的算法。贪心算法的核心思想是:在每一步选择中都采取当前状态下最优的选择,即贪心的做出局部最优的决策,从而希望最终能够得到全局最优解。尽…...
将产品照片(form.productPhotos)转为 JSON 字符串发送给后端
文章目录 1. 前端 form.productPhotos 的当前处理a. 组件绑定b. 当前发送逻辑 2. 如何将 form.productPhotos 转为 JSON 字符串发送给后端a. 修改前端 save() 方法b. 确保 esave API 支持接收字符串 基于你提供的 identify-form.vue 代码,我将分析如何将产品照片&a…...
『大模型笔记』详细对比GraphRAG与传统RAG!
详细对比GraphRAG与传统RAG! 文章目录 详细对比GraphRAG与传统RAG!要点最终内容1. GraphRAG的作用与应用场景2. GraphRAG与传统RAG的对比3. GraphRAG的工作原理4. GraphRAG如何提高准确性和提供完整答案5. GraphRAG在开发和维护中的优势6. GraphRAG对生产环境的影响7. GraphR…...
安全面试3
文章目录 一个单位的一级域名可能不止一个,怎么收集某个单位的所有域名,注意不是子域名用转义字符防御时,如果遇到数据库的列名或是表名本身就带着特殊字符,应该怎么做宽字节注入原理防御宽字节注入的方法 基于黑白名单的修复&…...
软件测试:1、单元测试
1. 单元测试的基本概念 单元(Unit):软件系统的基本组成单位,可以是函数、模块、方法或类。 单元测试(Unit Testing):对软件单元进行的测试,验证代码的正确性、规范性、安全性和性能…...
球队训练信息管理系统设计与实现(代码+数据库+LW)
摘 要 传统办法管理信息首先需要花费的时间比较多,其次数据出错率比较高,而且对错误的数据进行更改也比较困难,最后,检索数据费事费力。因此,在计算机上安装球队训练信息管理系统软件来发挥其高效地信息处理的作用&a…...
【Bluedroid】AVRCP 连接源码分析(二)
接着上一篇【Bluedroid】AVRCP 连接源码分析(一)-CSDN博客,继续AVRCP连接的源码分析。 getcapabilities_cmd packages/modules/Bluetooth/system/btif/src/btif_rc.cc /***************************************************************************** Function …...
OSS(对象存储服务)
OSS(对象存储服务) 是一种用于存储和管理非结构化数据的云存储服务,其核心设计面向海量数据的高扩展性、高可靠性和低成本存储。以下从定义、核心原理、架构特点和应用场景等方面详细介绍: 一、什么是OSS? OSS&#x…...
《深入理解JVM》实战笔记(二): 类加载机制与类加载器
序言 Java 语言的强大之处之一在于其动态加载的能力,使得 Java 程序可以在运行时加载新的类,而不需要在编译时确定所有的类信息。这一切都离不开 JVM 的类加载机制。本篇博客将详细探讨 JVM 的类加载过程以及类加载器的工作原理,帮助你更深入…...
ChromeDriver下载
平时为了下个驱动,到处找挺麻烦,收集了很多无偿分享给需要的人,仅供学习和交流。 ChromeDriver 102.0.5005.61 ChromeDriver 105.0.5195.102 ChromeDriver 108.0.5359.71 ChromeDriver 111.0.5563.64 ChromeDriver 116.0.5845.97 Chrom…...
[特殊字符] 智能合约中的数据是如何在区块链中保持一致的?
🧠 智能合约中的数据是如何在区块链中保持一致的? 为什么所有区块链节点都能得出相同结果?合约调用这么复杂,状态真能保持一致吗?本篇带你从底层视角理解“状态一致性”的真相。 一、智能合约的数据存储在哪里…...
网络六边形受到攻击
大家读完觉得有帮助记得关注和点赞!!! 抽象 现代智能交通系统 (ITS) 的一个关键要求是能够以安全、可靠和匿名的方式从互联车辆和移动设备收集地理参考数据。Nexagon 协议建立在 IETF 定位器/ID 分离协议 (…...
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以?
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以? 在 Golang 的面试中,map 类型的使用是一个常见的考点,其中对 key 类型的合法性 是一道常被提及的基础却很容易被忽视的问题。本文将带你深入理解 Golang 中…...
AI Agent与Agentic AI:原理、应用、挑战与未来展望
文章目录 一、引言二、AI Agent与Agentic AI的兴起2.1 技术契机与生态成熟2.2 Agent的定义与特征2.3 Agent的发展历程 三、AI Agent的核心技术栈解密3.1 感知模块代码示例:使用Python和OpenCV进行图像识别 3.2 认知与决策模块代码示例:使用OpenAI GPT-3进…...
今日科技热点速览
🔥 今日科技热点速览 🎮 任天堂Switch 2 正式发售 任天堂新一代游戏主机 Switch 2 今日正式上线发售,主打更强图形性能与沉浸式体验,支持多模态交互,受到全球玩家热捧 。 🤖 人工智能持续突破 DeepSeek-R1&…...
【JavaWeb】Docker项目部署
引言 之前学习了Linux操作系统的常见命令,在Linux上安装软件,以及如何在Linux上部署一个单体项目,大多数同学都会有相同的感受,那就是麻烦。 核心体现在三点: 命令太多了,记不住 软件安装包名字复杂&…...
微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据
微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据 Power Query 具有大量专门帮助您清理和准备数据以供分析的功能。 您将了解如何简化复杂模型、更改数据类型、重命名对象和透视数据。 您还将了解如何分析列,以便知晓哪些列包含有价值的数据,…...
使用Spring AI和MCP协议构建图片搜索服务
目录 使用Spring AI和MCP协议构建图片搜索服务 引言 技术栈概览 项目架构设计 架构图 服务端开发 1. 创建Spring Boot项目 2. 实现图片搜索工具 3. 配置传输模式 Stdio模式(本地调用) SSE模式(远程调用) 4. 注册工具提…...
莫兰迪高级灰总结计划简约商务通用PPT模版
莫兰迪高级灰总结计划简约商务通用PPT模版,莫兰迪调色板清新简约工作汇报PPT模版,莫兰迪时尚风极简设计PPT模版,大学生毕业论文答辩PPT模版,莫兰迪配色总结计划简约商务通用PPT模版,莫兰迪商务汇报PPT模版,…...
解读《网络安全法》最新修订,把握网络安全新趋势
《网络安全法》自2017年施行以来,在维护网络空间安全方面发挥了重要作用。但随着网络环境的日益复杂,网络攻击、数据泄露等事件频发,现行法律已难以完全适应新的风险挑战。 2025年3月28日,国家网信办会同相关部门起草了《网络安全…...
