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.pswp.cn/web/70448.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/70448.shtml
英文地址,請注明出處:http://en.pswp.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. 行情核心驅動因素 主線延續性&…