源码剖析之@Scheduled与ThreadPoolTaskScheduler

本文详细介绍了Spring框架中的@Scheduled注解及其与ScheduledThreadPoolExecutor在处理定时任务中的协同工作,通过源码分析展示了两者如何简化任务配置和保证执行效率。
摘要由CSDN通过智能技术生成

引言

在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 ThreadPoolExecutor
    implements 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并发编程中相辅相成,前者极大简化了定时任务的配置,后者则作为强大的底层引擎,确保了定时任务的高效稳定执行。通过深入理解它们的源码实现,开发者可以更好地掌握定时任务调度技术,优化程序性能,提升系统稳定性。

  • 37
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
@Scheduled注解的源码定义如下: ```java @Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Scheduled { String cron() default ""; String zone() default ""; long fixedDelay() default -1; String fixedDelayString() default ""; long fixedRate() default -1; String fixedRateString() default ""; long initialDelay() default -1; String initialDelayString() default ""; } ``` 在源码中,@Scheduled注解有几个属性可以使用: 1. cron: 定义cron表达式,用于指定任务的执行时间。 2. zone: 时区属性,用于指定任务的执行时区。 3. fixedDelay: 固定延迟时间,表示任务执行完成后,延迟指定的时间再执行下一次任务。 4. fixedDelayString: 固定延迟时间的字符串形式。 5. fixedRate: 固定速率时间,表示任务开始执行后,固定时间间隔执行下一次任务。 6. fixedRateString: 固定速率时间的字符串形式。 7. initialDelay: 初始延迟时间,表示任务启动后延迟指定的时间才开始执行第一次任务。 8. initialDelayString: 初始延迟时间的字符串形式。 需要注意的是,cron表达式必须由6个占位符组成(秒数、分钟、小时、日期、月份、星期),若使用7个占位符会报错。另外,可以使用*、/、-等符号来组合和表示时间间隔。具体的使用方法和示例可以参考引用、和中的内容。<span class="em">1</span><span class="em">2</span><span class="em">3</span><span class="em">4</span>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

吴代庄

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值