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,唤醒任意一个等待线程

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

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

相关文章

【跟我学YOLO】(1)YOLO12:以注意力为中心的物体检测

欢迎关注『跟我学 YOLO』系列 【跟我学YOLO】&#xff08;1&#xff09;YOLO12&#xff1a;以注意力为中心的物体检测] 0. YOLOv12 简介0.1 YOLO12 论文下载0.2 YOLO12 的主要改进0.3 YOLO12 支持的任务和性能0.4 论文摘要 1. 背景介绍2. 相关的工作3. 方法3.1 效率分析3.2 区域…

LTO优化详解

嵌入式开发过程中的LTO优化是什么意思&#xff1f;能够用来解决什么问题&#xff1f;它都优化了哪些&#xff1f;仅开启LTO真的可以优化SIZE的问题吗? 请分别回答我刚才提到的问题&#xff1f; 嗯&#xff0c;用户问关于嵌入式开发中的LTO优化&#xff0c;我需要详细解释这个概…

leetcode_位运算 231.2的幂

231. 2的幂 给你一个整数n&#xff0c;请你判断该整数是否是 2 的幂次方。如果是&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false。 如果存在一个整数 x 使得 n 2x &#xff0c;则认为 n 是 2 的幂次方 思路: 2的幂运算的结果中&#xff0c;其二进制数里只有…

VSCode - VSCode 切换自动换行

VSCode 自动换行 1、基本介绍 在 VSCode 中&#xff0c;启用自动换行可以让长行代码自动折行显示&#xff0c;避免水平滚动条频繁使用&#xff0c;提升代码阅读体验 如果禁用自动换行&#xff0c;长行代码就需要手动结合水平滚动条来阅读 2、演示 启用自动换行 禁用自动换…

CSS `transform` 属性详解:打造视觉效果与动画的利器

CSS transform 属性详解&#xff1a;打造视觉效果与动画的利器 引言一、transform 属性简介二、平移&#xff08;Translation&#xff09;三、旋转&#xff08;Rotation&#xff09;四、缩放&#xff08;Scale&#xff09;五、倾斜&#xff08;Skew&#xff09;六、组合变换&am…

算法每日一练 (5)

&#x1f4a2;欢迎来到张胤尘的技术站 &#x1f4a5;技术如江河&#xff0c;汇聚众志成。代码似星辰&#xff0c;照亮行征程。开源精神长&#xff0c;传承永不忘。携手共前行&#xff0c;未来更辉煌&#x1f4a5; 文章目录 算法每日一练 (5)旋转链表题目描述解题思路解题代码c/…

51单片机-按键

1、独立按键 1.1、按键介绍 轻触开关是一种电子开关&#xff0c;使用时&#xff0c;轻轻按开关按钮就可使开关接通&#xff0c;当松开手时&#xff0c;开关断开。 1.2、独立按键原理 按键在闭合和断开时&#xff0c;触点会存在抖动现象。P2\P3\P1都是准双向IO口&#xff0c;…

BFS 和 DFS(深度优先搜索、广度优先搜索)

深度优先搜索&#xff08;DFS&#xff09;和广度优先搜索&#xff08;BFS&#xff09;是两种常用的图遍历算法&#xff0c;用于解决图相关的问题。它们在搜索问题中具有广泛的应用&#xff0c;如路径搜索、连通性检测等。 以下是具体区别&#xff1a; &#xff08;图片引自&am…

推荐几款较好的开源成熟框架

一. 若依&#xff1a; 1. 官方网站&#xff1a;https://doc.ruoyi.vip/ruoyi/ 2. 若依SpringBootVueElement 的后台管理系统&#xff1a;https://gitee.com/y_project/RuoYi-Vue 3. 若依SpringBootVueElement 的后台管理系统&#xff1a;https://gitee.com/y_project/RuoYi-Cl…

根据音频中的不同讲述人声音进行分离音频 | 基于ai的说话人声音分离项目

0.研究背景 在实际的开发中可能会遇到这样的问题&#xff0c;老板让你把音频中的每个讲话人的声音分离成不同的音频片段。你可以使用au等专业的音频处理软件手动分离。但是这样效率太慢了&#xff0c;现在ai这么发达&#xff0c;我们能否借助ai之力来分离一条音频中的不同的说…

本地化部署 DeepSeek:从零到一的完整指南

本地化部署 DeepSeek&#xff1a;从零到一的完整指南 个人主页&#xff1a;顾漂亮 文章专栏&#xff1a;AI学习 目录 引言什么是 DeepSeek&#xff1f;为什么选择本地化部署&#xff1f;DeepSeek 本地化部署的前期准备 硬件需求软件需求环境配置 DeepSeek 本地化部署步骤 步骤…

使用ArcGIS Pro自动矢量化水系

在地理信息系统&#xff08;GIS&#xff09;领域&#xff0c;自动矢量化是一项至关重要的技术&#xff0c;它能够将栅格图像中的要素转换为矢量数据&#xff0c;从而方便后续的分析和处理。本文将详细介绍如何使用ArcGIS Pro自动矢量化水系&#xff0c;适用于那些颜色相对统一、…

C++类和对象进阶:初始化列表和static成员深度详解

C类和对象&#xff1a;初始化列表和static成员深度详解 1. 前言2. 构造函数初始化成员变量的方式2.1 构造函数体内赋值2.2 初始化列表2.2.1 初始化列表的注意事项 2.3 初始化列表的初始化顺序 3. 类的静态成员3.1 引入3.2 静态成员变量3.3 静态成员函数3.4 静态成员的注意事项3…

ubuntu ffmpeg 安装踩坑

ffmpeg 安装踩坑 安装命令: sudo apt update sudo apt install ffmpeg如果以上命令没有报错&#xff0c;那么恭喜你很幸运&#xff0c;可以关闭这篇文章了&#xff01; 如果跟我一样&#xff0c;遇到如下报错&#xff0c;可以接着往下看&#xff1a; 报错信息&#xff1a; …

第13章 int指令

目录 13.1 int 指令13.2 编写供应用程序调用的中断例程13.3 对int、iret和栈的深入理解13.4 BIOS和DOS所提供的中断例程13.5 BIOS和DOS中断例程的安装过程13.6 BIOS中断例程应用13.7 DOS中断例程应用实验13 编写、应用中断例程 中断信息可以来自CPU的内部和外部&#xff0c;当C…

最新扣子(Coze)案例教程:全自动DeepSeek 写影评+批量生成 + 发布飞书,提效10 倍!手把手教学,完全免费教程

&#x1f468;‍&#x1f4bb;群里有同学是做影视赛道的博主&#xff0c;听说最近DeepSeek这么火&#xff0c;咨询能不能用DeepSeek写影评&#xff0c;并整理电影数据资料&#xff0c;自动发布到飞书文档&#xff0c;把每天的工作做成一个自动化的流程。 那今天斜杠君就为大家…

DeepSeek 提示词:定义、作用、分类与设计原则

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家&#xff0c;历代文学网&#xff08;PC端可以访问&#xff1a;https://literature.sinhy.com/#/?__c1000&#xff0c;移动端可微信小程序搜索“历代文学”&#xff09;总架构师&#xff0c;15年工作经验&#xff0c;精通Java编…

鸟语林-论坛系统自动化测试

文章目录 一、自动化实施步骤1.1编写Web测试用例1.2 编写自动化代码1.2.1 LoginPageTest1) 能否正确打开登录页面2) 点击去注册能否跳转注册页面3) 模拟用户登录&#xff0c;输入多组登录测试用例 1.2.2 RegisterPageTest1) 能否成功打开注册页面2) 注册测试用例3) 点击去登录按…

DeepSeek模型量化

技术背景 大语言模型&#xff08;Large Language Model&#xff0c;LLM&#xff09;&#xff0c;可以通过量化&#xff08;Quantization&#xff09;操作来节约内存/显存的使用&#xff0c;并且降低了通讯开销&#xff0c;进而达到加速模型推理的效果。常见的就是把Float16的浮…

本周行情——250222

本周A股行情展望与策略 结合近期盘面特征及市场主线演化&#xff0c;本周A股预计延续结构性分化行情&#xff0c;科技成长与政策催化板块仍是资金主战场&#xff0c;但需警惕高标股分歧带来的波动。以下是具体分析与策略建议&#xff1a; 1. 行情核心驱动因素 主线延续性&…