源码剖析之@Scheduled与ThreadPoolTaskScheduler

2024-03-29 13:28

本文主要是介绍源码剖析之@Scheduled与ThreadPoolTaskScheduler,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

引言

在Java并发编程领域,@Scheduled注解和ScheduledThreadPoolExecutor是两个关键组件,前者在Spring框架中被用来简化定时任务的配置与执行,后者则是JDK内置的用于处理周期性和延时任务的线程池。本文将透过源码视角,详尽解析二者的工作机制及其内在联系。

一、@Scheduled

使用方式

在Spring中,我们可以使用@Schedule注解来定义一个定时任务方法。例如:

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;@Component
public class MyTask {@Scheduled(fixedRate = 5000)public void doSomething() {System.out.println("执行任务");}
}

源码分析

虽然@Scheduled注解并不直接涉及ScheduledThreadPoolExecutor的源码,但它是与Spring框架中定时任务调度密切相关的。@Scheduled注解允许我们在方法级别定义定时任务:

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Scheduled {// ...String cron() default "";long fixedRate() default -1L;long fixedDelay() default -1L;
}

在这里插入图片描述
ScheduledAnnotationBeanPostProcessor 是 Spring 框架中用于处理带有 @Scheduled 注解的任务类的核心类。它主要用于扫描和管理所有被 @Scheduled 注解标记的方法,使其按照设定的时间计划自动执行。

  1. 继承 ScheduledTaskHolder:这个接口主要提供对所有已注册的计划任务的访问能力。通过实现这个接口,ScheduledAnnotationBeanPostProcessor 可以作为持有计划任务的容器,可以查询、管理这些基于注解的任务。

  2. 实现 ApplicationListener<E>:Spring 中的事件监听器接口,这意味着 ScheduledAnnotationBeanPostProcessor 可以监听应用上下文中的特定事件(如 ContextRefreshedEvent 等)。当接收到相关事件时,它可以进行相应的响应操作,例如在 Spring 容器初始化完成后开始执行计划任务。

  3. 实现 SmartInitializingSingleton:这是 Spring 内部的一个接口,表示一个在 Spring 应用上下文完全初始化后需要执行一次性初始化逻辑的对象。对于 ScheduledAnnotationBeanPostProcessor 来说,就是在 Spring 容器完成初始化之后,开始调度那些带有 @Scheduled 注解的任务。

注解注入源码

public Object postProcessAfterInitialization(Object bean, String beanName) {if (!(bean instanceof AopInfrastructureBean) && !(bean instanceof TaskScheduler) && !(bean instanceof ScheduledExecutorService)) {Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean);if (!this.nonAnnotatedClasses.contains(targetClass) && AnnotationUtils.isCandidateClass(targetClass, Arrays.asList(Scheduled.class, Schedules.class))) {Map<Method, Set<Scheduled>> annotatedMethods = MethodIntrospector.selectMethods(targetClass, (method) -> {Set<Scheduled> scheduledAnnotations = AnnotatedElementUtils.getMergedRepeatableAnnotations(method, Scheduled.class, Schedules.class);return !scheduledAnnotations.isEmpty() ? scheduledAnnotations : null;});if (annotatedMethods.isEmpty()) {this.nonAnnotatedClasses.add(targetClass);if (this.logger.isTraceEnabled()) {this.logger.trace("No @Scheduled annotations found on bean class: " + targetClass);}} else {annotatedMethods.forEach((method, scheduledAnnotations) -> {scheduledAnnotations.forEach((scheduled) -> {this.processScheduled(scheduled, method, bean);});});if (this.logger.isTraceEnabled()) {this.logger.trace(annotatedMethods.size() + " @Scheduled methods processed on bean '" + beanName + "': " + annotatedMethods);}}}return bean;} else {return bean;}}

这段代码是 Spring 框架中 ScheduledAnnotationBeanPostProcessor 类的 postProcessAfterInitialization 方法实现,它是 Spring 的一个 Bean 后处理器方法。在 Spring 容器初始化 Bean 过程中调用此方法来处理带有 @Scheduled 注解的方法。

方法的主要逻辑如下:

  1. 首先检查传入的 bean 是否是 AOP 基础结构相关的 Bean,或者是 TaskSchedulerScheduledExecutorService 的实例。如果是,则直接返回原 bean,不进行后续处理。

  2. 如果不是上述类型,则获取 Bean 的最终目标类(即如果 Bean 是代理对象,则获取其代理的真实类),并判断该类是否包含 ScheduledSchedules 注解,且未被先前处理过。

  3. 使用 MethodIntrospector 选择出目标类中所有带有 ScheduledSchedules 注解的方法,并将注解信息存入 Map<Method, Set<Scheduled>> 结构中。

  4. 如果没有找到任何带有 Scheduled 注解的方法,则将目标类添加到已知不包含注解的类集合中,并记录一条日志信息。

  5. 如果找到了带有 Scheduled 注解的方法,则遍历这些方法及其对应的注解集合并调用 processScheduled 方法处理每个注解,从而将该方法配置为一个定时任务。

  6. 最后,在日志级别允许的情况下,输出已处理的带有 @Scheduled 注解的方法数量以及详细信息。

二、ScheduledThreadPoolExecutor

使用方式

以下是一个简单的使用ScheduledThreadPoolExecutor执行定时任务的示例:

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;public class MyTask {public static void main(String[] args) {ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);Runnable task = () -> System.out.println("执行任务");executor.scheduleAtFixedRate(task, 0, 5, TimeUnit.SECONDS);}
}

上述代码中,我们创建了一个包含1个线程的ScheduledThreadPoolExecutor,并使用scheduleAtFixedRate方法创建一个定时任务,每隔5秒执行一次。

源码分析

在这里插入图片描述

ScheduledThreadPoolExecutor是Java并发库的核心类之一,它扩展了ThreadPoolExecutor,并实现了ScheduledExecutorService接口,专门用于处理定时任务。

public class ScheduledThreadPoolExecutor extends ThreadPoolExecutorimplements ScheduledExecutorService {// ...public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) {// ...}public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) {// ...}public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnit unit) {// ...}public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,long initialDelay,long delay,TimeUnit unit) {// ...}// ...
}

ScheduledThreadPoolExecutor主要提供了四种方法用于创建不同类型的定时任务,包括一次性延时执行、一次性定时执行以及按固定速率或固定延迟周期执行。内部实现通过维护一个优先级队列(通常是DelayedWorkQueue)来排序待执行的任务,并利用一个后台线程不断地从队列中取出到期的任务执行。

ScheduledThreadPoolExecutor继承自ThreadPoolExecutor,它的主要特点是支持定时任务。在内部,ScheduledThreadPoolExecutor维护了一个优先队列,用于存储待执行的任务。当任务到达指定的执行时间时,它会从优先队列中取出任务并执行。

ScheduledThreadPoolExecutor提供了两种定时任务的执行策略:scheduleAtFixedRate和scheduleWithFixedDelay。前者表示任务按照固定的速率执行,后者表示任务在上一次任务完成后等待固定的延迟后执行。

三、@Scheduled与ScheduledThreadPoolExecutor的协同工作

在Spring框架中,@Scheduled注解定义的定时任务会被转换为ScheduledThreadPoolExecutor所能识别的形式,然后提交给ScheduledThreadPoolExecutor执行。例如,对于@Scheduled(cron = "0 0/5 * * * ?")这样的cron表达式定时任务,Spring会将其解析并映射到ScheduledThreadPoolExecutor的周期性执行方法上。

总的来说,@Scheduled注解和ScheduledThreadPoolExecutor在Java并发编程中相辅相成,前者极大简化了定时任务的配置,后者则作为强大的底层引擎,确保了定时任务的高效稳定执行。通过深入理解它们的源码实现,开发者可以更好地掌握定时任务调度技术,优化程序性能,提升系统稳定性。

这篇关于源码剖析之@Scheduled与ThreadPoolTaskScheduler的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/858713

相关文章

Go中sync.Once源码的深度讲解

《Go中sync.Once源码的深度讲解》sync.Once是Go语言标准库中的一个同步原语,用于确保某个操作只执行一次,本文将从源码出发为大家详细介绍一下sync.Once的具体使用,x希望对大家有... 目录概念简单示例源码解读总结概念sync.Once是Go语言标准库中的一个同步原语,用于确保某个操

Springboot的ThreadPoolTaskScheduler线程池轻松搞定15分钟不操作自动取消订单

《Springboot的ThreadPoolTaskScheduler线程池轻松搞定15分钟不操作自动取消订单》:本文主要介绍Springboot的ThreadPoolTaskScheduler线... 目录ThreadPoolTaskScheduler线程池实现15分钟不操作自动取消订单概要1,创建订单后

Java汇编源码如何查看环境搭建

《Java汇编源码如何查看环境搭建》:本文主要介绍如何在IntelliJIDEA开发环境中搭建字节码和汇编环境,以便更好地进行代码调优和JVM学习,首先,介绍了如何配置IntelliJIDEA以方... 目录一、简介二、在IDEA开发环境中搭建汇编环境2.1 在IDEA中搭建字节码查看环境2.1.1 搭建步

Node.js 中 http 模块的深度剖析与实战应用小结

《Node.js中http模块的深度剖析与实战应用小结》本文详细介绍了Node.js中的http模块,从创建HTTP服务器、处理请求与响应,到获取请求参数,每个环节都通过代码示例进行解析,旨在帮... 目录Node.js 中 http 模块的深度剖析与实战应用一、引言二、创建 HTTP 服务器:基石搭建(一

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

Java ArrayList扩容机制 (源码解读)

结论:初始长度为10,若所需长度小于1.5倍原长度,则按照1.5倍扩容。若不够用则按照所需长度扩容。 一. 明确类内部重要变量含义         1:数组默认长度         2:这是一个共享的空数组实例,用于明确创建长度为0时的ArrayList ,比如通过 new ArrayList<>(0),ArrayList 内部的数组 elementData 会指向这个 EMPTY_EL

如何在Visual Studio中调试.NET源码

今天偶然在看别人代码时,发现在他的代码里使用了Any判断List<T>是否为空。 我一般的做法是先判断是否为null,再判断Count。 看了一下Count的源码如下: 1 [__DynamicallyInvokable]2 public int Count3 {4 [__DynamicallyInvokable]5 get

工厂ERP管理系统实现源码(JAVA)

工厂进销存管理系统是一个集采购管理、仓库管理、生产管理和销售管理于一体的综合解决方案。该系统旨在帮助企业优化流程、提高效率、降低成本,并实时掌握各环节的运营状况。 在采购管理方面,系统能够处理采购订单、供应商管理和采购入库等流程,确保采购过程的透明和高效。仓库管理方面,实现库存的精准管理,包括入库、出库、盘点等操作,确保库存数据的准确性和实时性。 生产管理模块则涵盖了生产计划制定、物料需求计划、

Spring 源码解读:自定义实现Bean定义的注册与解析

引言 在Spring框架中,Bean的注册与解析是整个依赖注入流程的核心步骤。通过Bean定义,Spring容器知道如何创建、配置和管理每个Bean实例。本篇文章将通过实现一个简化版的Bean定义注册与解析机制,帮助你理解Spring框架背后的设计逻辑。我们还将对比Spring中的BeanDefinition和BeanDefinitionRegistry,以全面掌握Bean注册和解析的核心原理。

音视频入门基础:WAV专题(10)——FFmpeg源码中计算WAV音频文件每个packet的pts、dts的实现

一、引言 从文章《音视频入门基础:WAV专题(6)——通过FFprobe显示WAV音频文件每个数据包的信息》中我们可以知道,通过FFprobe命令可以打印WAV音频文件每个packet(也称为数据包或多媒体包)的信息,这些信息包含该packet的pts、dts: 打印出来的“pts”实际是AVPacket结构体中的成员变量pts,是以AVStream->time_base为单位的显