当前位置: 首页 > news >正文

Spring Boot定时任务原理

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) {// 处理 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
  • 时间参数解析:
    • 支持 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) {// 创建 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();}}
}
  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,唤醒任意一个等待线程

相关文章:

Spring Boot定时任务原理

Spring Boot定时任务原理 在现代应用中&#xff0c;定时任务的调度是实现周期性操作的关键机制。Spring Boot 提供了强大的定时任务支持&#xff0c;通过注解驱动的方式&#xff0c;开发者可以轻松地为方法添加定时任务功能。本文将深入探讨 Spring Boot 中定时任务的实现原理…...

C#初级教程(7)——初级期末检测

练习 1&#xff1a;计算圆的周长和面积 改编题目&#xff1a;编写一个 C# 程序&#xff0c;让用户输入圆的半径&#xff0c;然后计算并输出该圆的周长和面积&#xff0c;结果保留两位小数。 using System;class CircleCalculation {static void Main(){const double pi 3.14…...

原生稀疏注意力机制(NSA):硬件对齐且可原生训练的稀疏注意力机制-论文阅读

摘要 长上下文建模对于下一代语言模型至关重要&#xff0c;但标准注意力机制的高计算成本带来了巨大的计算挑战。稀疏注意力提供了一种在保持模型能力的同时提高效率的有前途的方向。本文提出了一种名为 NSA&#xff08;原生可训练稀疏注意力机制&#xff09; 的方法&#xff…...

Apache Struts RCE (CVE-2024-53677)

前言 对目前的Apache Struts RCE (CVE-2024-53677)的poc进行总结&#xff0c;由于只能单个ip验证&#xff0c;所以自己更改一下代码&#xff0c;实现&#xff1a;多线程读取url验证并保存&#xff0c;更改为中文解释 免责声明 请勿利用文章内的相关技术从事非法测试&#xf…...

GIS地图、轨道交通与智能驾驶UI设计:未来交通的智能化探索

随着科技的飞速发展&#xff0c;我们正迎来一个高度智能化的未来。在这个时代背景下&#xff0c;GIS&#xff08;地理信息系统&#xff09;、轨道交通以及智能驾驶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 类型&#xff0c;要将 ED25519 私钥转换为 RSA 私钥&#xff0c;需要重新生成一个新的 RSA 密钥对。 步骤&#xff1a; 生成新的 RSA 密钥对 使用 ssh-keygen 来生成一个新的 RSA 密钥对。比如&#xff0c;执…...

SQLMesh 系列教程7- 详解 seed 模型

SQLMesh 是一个强大的数据建模和管道管理工具&#xff0c;允许用户通过 SQL 语句定义数据模型并进行版本控制。Seed 模型是 SQLMesh 中的一种特殊模型&#xff0c;主要用于初始化和填充基础数据集。它通常包含静态数据&#xff0c;如参考数据和配置数据&#xff0c;旨在为后续的…...

Git常见命令--助力开发

git常见命令&#xff1a; 创建初始化仓库&#xff1a; git 将文件提交到暂存区 git add 文件名 将文件提交到工作区 git commit -m "注释&#xff08;例如这是发行的版本1&#xff09;" 文件名 查看状态 如果暂存区没有文件被提交显示&#xff1a; $ 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…...

算法系列之贪心算法

在算法中&#xff0c;贪心算法&#xff08;Greedy Algorithm&#xff09;是一种常见的解决优化问题的算法。贪心算法的核心思想是&#xff1a;在每一步选择中都采取当前状态下最优的选择&#xff0c;即贪心的做出局部最优的决策&#xff0c;从而希望最终能够得到全局最优解。尽…...

将产品照片(form.productPhotos)转为 JSON 字符串发送给后端

文章目录 1. 前端 form.productPhotos 的当前处理a. 组件绑定b. 当前发送逻辑 2. 如何将 form.productPhotos 转为 JSON 字符串发送给后端a. 修改前端 save() 方法b. 确保 esave API 支持接收字符串 基于你提供的 identify-form.vue 代码&#xff0c;我将分析如何将产品照片&a…...

『大模型笔记』详细对比GraphRAG与传统RAG!

详细对比GraphRAG与传统RAG! 文章目录 详细对比GraphRAG与传统RAG!要点最终内容1. GraphRAG的作用与应用场景2. GraphRAG与传统RAG的对比3. GraphRAG的工作原理4. GraphRAG如何提高准确性和提供完整答案5. GraphRAG在开发和维护中的优势6. GraphRAG对生产环境的影响7. GraphR…...

安全面试3

文章目录 一个单位的一级域名可能不止一个&#xff0c;怎么收集某个单位的所有域名&#xff0c;注意不是子域名用转义字符防御时&#xff0c;如果遇到数据库的列名或是表名本身就带着特殊字符&#xff0c;应该怎么做宽字节注入原理防御宽字节注入的方法 基于黑白名单的修复&…...

软件测试:1、单元测试

1. 单元测试的基本概念 单元&#xff08;Unit&#xff09;&#xff1a;软件系统的基本组成单位&#xff0c;可以是函数、模块、方法或类。 单元测试&#xff08;Unit Testing&#xff09;&#xff1a;对软件单元进行的测试&#xff0c;验证代码的正确性、规范性、安全性和性能…...

球队训练信息管理系统设计与实现(代码+数据库+LW)

摘 要 传统办法管理信息首先需要花费的时间比较多&#xff0c;其次数据出错率比较高&#xff0c;而且对错误的数据进行更改也比较困难&#xff0c;最后&#xff0c;检索数据费事费力。因此&#xff0c;在计算机上安装球队训练信息管理系统软件来发挥其高效地信息处理的作用&a…...

【Bluedroid】AVRCP 连接源码分析(二)

接着上一篇【Bluedroid】AVRCP 连接源码分析(一)-CSDN博客,继续AVRCP连接的源码分析。 getcapabilities_cmd packages/modules/Bluetooth/system/btif/src/btif_rc.cc /***************************************************************************** Function …...

OSS(对象存储服务)

OSS&#xff08;对象存储服务&#xff09; 是一种用于存储和管理非结构化数据的云存储服务&#xff0c;其核心设计面向海量数据的高扩展性、高可靠性和低成本存储。以下从定义、核心原理、架构特点和应用场景等方面详细介绍&#xff1a; 一、什么是OSS&#xff1f; OSS&#x…...

《深入理解JVM》实战笔记(二): 类加载机制与类加载器

序言 Java 语言的强大之处之一在于其动态加载的能力&#xff0c;使得 Java 程序可以在运行时加载新的类&#xff0c;而不需要在编译时确定所有的类信息。这一切都离不开 JVM 的类加载机制。本篇博客将详细探讨 JVM 的类加载过程以及类加载器的工作原理&#xff0c;帮助你更深入…...

ChromeDriver下载

平时为了下个驱动&#xff0c;到处找挺麻烦&#xff0c;收集了很多无偿分享给需要的人&#xff0c;仅供学习和交流。 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…...

网络编程(Modbus进阶)

思维导图 Modbus RTU&#xff08;先学一点理论&#xff09; 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议&#xff0c;由 Modicon 公司&#xff08;现施耐德电气&#xff09;于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…...

深度学习在微纳光子学中的应用

深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向&#xff1a; 逆向设计 通过神经网络快速预测微纳结构的光学响应&#xff0c;替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…...

Chapter03-Authentication vulnerabilities

文章目录 1. 身份验证简介1.1 What is authentication1.2 difference between authentication and authorization1.3 身份验证机制失效的原因1.4 身份验证机制失效的影响 2. 基于登录功能的漏洞2.1 密码爆破2.2 用户名枚举2.3 有缺陷的暴力破解防护2.3.1 如果用户登录尝试失败次…...

云原生核心技术 (7/12): K8s 核心概念白话解读(上):Pod 和 Deployment 究竟是什么?

大家好&#xff0c;欢迎来到《云原生核心技术》系列的第七篇&#xff01; 在上一篇&#xff0c;我们成功地使用 Minikube 或 kind 在自己的电脑上搭建起了一个迷你但功能完备的 Kubernetes 集群。现在&#xff0c;我们就像一个拥有了一块崭新数字土地的农场主&#xff0c;是时…...

Xshell远程连接Kali(默认 | 私钥)Note版

前言:xshell远程连接&#xff0c;私钥连接和常规默认连接 任务一 开启ssh服务 service ssh status //查看ssh服务状态 service ssh start //开启ssh服务 update-rc.d ssh enable //开启自启动ssh服务 任务二 修改配置文件 vi /etc/ssh/ssh_config //第一…...

QMC5883L的驱动

简介 本篇文章的代码已经上传到了github上面&#xff0c;开源代码 作为一个电子罗盘模块&#xff0c;我们可以通过I2C从中获取偏航角yaw&#xff0c;相对于六轴陀螺仪的yaw&#xff0c;qmc5883l几乎不会零飘并且成本较低。 参考资料 QMC5883L磁场传感器驱动 QMC5883L磁力计…...

【Go】3、Go语言进阶与依赖管理

前言 本系列文章参考自稀土掘金上的 【字节内部课】公开课&#xff0c;做自我学习总结整理。 Go语言并发编程 Go语言原生支持并发编程&#xff0c;它的核心机制是 Goroutine 协程、Channel 通道&#xff0c;并基于CSP&#xff08;Communicating Sequential Processes&#xff0…...

Cloudflare 从 Nginx 到 Pingora:性能、效率与安全的全面升级

在互联网的快速发展中&#xff0c;高性能、高效率和高安全性的网络服务成为了各大互联网基础设施提供商的核心追求。Cloudflare 作为全球领先的互联网安全和基础设施公司&#xff0c;近期做出了一个重大技术决策&#xff1a;弃用长期使用的 Nginx&#xff0c;转而采用其内部开发…...

Neo4j 集群管理:原理、技术与最佳实践深度解析

Neo4j 的集群技术是其企业级高可用性、可扩展性和容错能力的核心。通过深入分析官方文档,本文将系统阐述其集群管理的核心原理、关键技术、实用技巧和行业最佳实践。 Neo4j 的 Causal Clustering 架构提供了一个强大而灵活的基石,用于构建高可用、可扩展且一致的图数据库服务…...

PL0语法,分析器实现!

简介 PL/0 是一种简单的编程语言,通常用于教学编译原理。它的语法结构清晰,功能包括常量定义、变量声明、过程(子程序)定义以及基本的控制结构(如条件语句和循环语句)。 PL/0 语法规范 PL/0 是一种教学用的小型编程语言,由 Niklaus Wirth 设计,用于展示编译原理的核…...