引言
在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
注解标记的方法,使其按照设定的时间计划自动执行。
-
继承
ScheduledTaskHolder
:这个接口主要提供对所有已注册的计划任务的访问能力。通过实现这个接口,ScheduledAnnotationBeanPostProcessor
可以作为持有计划任务的容器,可以查询、管理这些基于注解的任务。 -
实现
ApplicationListener<E>
:Spring 中的事件监听器接口,这意味着ScheduledAnnotationBeanPostProcessor
可以监听应用上下文中的特定事件(如 ContextRefreshedEvent 等)。当接收到相关事件时,它可以进行相应的响应操作,例如在 Spring 容器初始化完成后开始执行计划任务。 -
实现
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
注解的方法。
方法的主要逻辑如下:
-
首先检查传入的
bean
是否是 AOP 基础结构相关的 Bean,或者是TaskScheduler
或ScheduledExecutorService
的实例。如果是,则直接返回原bean
,不进行后续处理。 -
如果不是上述类型,则获取 Bean 的最终目标类(即如果 Bean 是代理对象,则获取其代理的真实类),并判断该类是否包含
Scheduled
或Schedules
注解,且未被先前处理过。 -
使用
MethodIntrospector
选择出目标类中所有带有Scheduled
或Schedules
注解的方法,并将注解信息存入Map<Method, Set<Scheduled>>
结构中。 -
如果没有找到任何带有
Scheduled
注解的方法,则将目标类添加到已知不包含注解的类集合中,并记录一条日志信息。 -
如果找到了带有
Scheduled
注解的方法,则遍历这些方法及其对应的注解集合并调用processScheduled
方法处理每个注解,从而将该方法配置为一个定时任务。 -
最后,在日志级别允许的情况下,输出已处理的带有
@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并发编程中相辅相成,前者极大简化了定时任务的配置,后者则作为强大的底层引擎,确保了定时任务的高效稳定执行。通过深入理解它们的源码实现,开发者可以更好地掌握定时任务调度技术,优化程序性能,提升系统稳定性。