ThreadPoolTaskScheduler 是 Spring Framework 提供的一个任务调度器,用于在多线程环境下执行定时任务。它基于线程池来管理任务的执行,可以根据配置的参数来控制线程池的大小、任务队列的大小等,从而灵活地控制任务的并发执行情况。
ThreadPoolTaskScheduler 的运行原理主要包括以下几个关键点:
线程池管理:ThreadPoolTaskScheduler 使用 ThreadPoolExecutor 作为底层的线程池管理器。通过配置 corePoolSize 和 maxPoolSize 可以控制线程池的核心线程数和最大线程数。当任务数超过核心线程数时,新任务会被放入任务队列中等待执行,直到队列满后才会创建新的线程执行任务,直到达到最大线程数。
任务调度:ThreadPoolTaskScheduler 使用 ScheduledExecutorService 来实现任务的调度功能。它可以执行延迟任务和周期性任务,通过调用 schedule 和 scheduleAtFixedRate 等方法来提交任务,然后由线程池中的线程来执行。
任务队列管理:ThreadPoolTaskScheduler 使用 BlockingQueue 来管理任务队列。可以通过配置 queueCapacity 来控制任务队列的大小,以避免任务过多导致内存溢出或性能下降的问题。
异常处理:ThreadPoolTaskScheduler 提供了异常处理机制,可以通过设置 errorHandler 属性来处理任务执行过程中的异常情况。默认情况下,异常会被打印到日志中,但也可以自定义实现 ErrorHandler 接口来处理异常。
1. SchedulingConfigurer
在开始介绍ThreadPoolTaskScheduler ,先从SchedulingConfigurer入手,任何自定义定时任务调度器,都要实现这个接口。
1.1 SchedulingConfigurer包括了一个方法configureTasks。
1.2 configureTasks 传入了一个ScheduledTaskRegistrar类型的对象,,通过这个对象可以配置任务调度器的各种属性。ScheduledTaskRegistrar包含了一个方法setScheduler。
1.3 可以调用 setScheduler 方法设置自定义的任务调度器实现,也可以调用 addTriggerTask 和 addCronTask 方法添加触发器任务和 cron 表达式任务。
1.4 setScheduler 方法传入了一个当前类的TaskScheduler对象。
2. TaskScheduler
askScheduler 接口是 Spring Framework 提供的一个任务调度器接口,用于管理和执行任务。
2.1 TaskScheduler接口的私有方法
schedule(Runnable task, Trigger trigger):使用给定的触发器安排一个单次执行的任务。触发器定义了任务的执行时间。
schedule(Runnable task, Date startTime):安排一个在给定的开始时间执行的任务。
scheduleAtFixedRate(Runnable task, Date startTime, long period):安排一个在给定的开始时间开始执行,然后以固定速率执行的任务。
scheduleAtFixedRate(Runnable task, long period):安排一个以给定的固定速率执行的任务,初始延迟为0。
scheduleWithFixedDelay(Runnable task, Date startTime, long delay):安排一个在给定的开始时间开始执行,然后在每次执行结束和下一次执行开始之间存在给定延迟的任务。
scheduleWithFixedDelay(Runnable task, long delay):安排一个在给定的初始延迟后开始执行,并在每次执行结束和下一次执行开始之间存在给定延迟的任务。
这些私有方法是用于实现任务调度功能的核心方法,通过这些方法可以实现不同类型的任务调度。
注:这里涉及到一个具体的方法schedule,这里在后续实现类会介绍到。
2.2 TaskScheduler接口实现类
常见的几种 TaskScheduler 接口的实现方法包括:
ThreadPoolTaskScheduler:基于线程池的任务调度器实现,可以通过配置线程池的参数来控制任务的并发执行情况。
ConcurrentTaskScheduler:简单的任务调度器实现,直接使用当前线程来执行任务,适用于不需要并发执行的任务场景。
ScheduledExecutorTaskScheduler:基于 ScheduledExecutorService 的任务调度器实现,类似于 ThreadPoolTaskScheduler,但没有线程池的管理功能。
CustomTaskScheduler:自定义的任务调度器实现,通过实现 TaskScheduler 接口的方法来实现自定义的调度逻辑,可以根据具体的业务需求来定制任务调度器的行为。
3. ThreadPoolTaskScheduler
3.1 ThreadPoolTaskScheduler继承了ConcurrentTaskExecutor
3.2 该类当中有一个私有成员scheduledExecutor,他的初始化如下:
3.3 继续跟踪:
3.4 可以看到,最后初始化了一个指定核心线程数的线程池,任务调度就是通过set线程池变量来实现的。
4. 自定义任务调度demo
以上介绍了SchedulingConfigurer / TaskScheduler / ThreadPoolTaskScheduler源码调用流程,以下是一个简单的示例,展示了如何实现 SchedulingConfigurer 接口来配置任务调度器。
@Configuration
@EnableScheduling
public class MySchedulingConfigurer implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(taskExecutor());
taskRegistrar.addTriggerTask(
() -> {
// 执行任务逻辑
},
triggerContext -> {
// 定义任务触发规则
CronTrigger cronTrigger = new CronTrigger("0 0/5 * * * ?");
return cronTrigger.nextExecutionTime(triggerContext);
}
);
}
@Bean(destroyMethod="shutdown")
public ThreadPoolTaskScheduler taskExecutor() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(10);
scheduler.setThreadNamePrefix("my-scheduled-task-");
scheduler.setWaitForTasksToCompleteOnShutdown(true);
scheduler.setAwaitTerminationSeconds(60);
return scheduler;
}
}
5 如何提交任务(重点★★★★★)
5.1 ok,还记得2.1节,在TaskScheduler接口当中,有一个方法schedule吗!
我们还是以ThreadPoolTaskScheduler实现类为例。
5.2 追踪开始
注意,这里调用了ReschedulingRunnable的schedule方法!!!
5.3 观察schedule的实现过程。
this.trigger.nextExecutionTime(this.triggerContext);根据当前的触发器 (trigger) 和触发器上下文 (triggerContext) 计算下一次任务执行的时间,并将结果赋值给 scheduledExecutionTime。
整个方法是根据触发器计算下一次任务的执行时间,然后使用线程池调度器安排任务在计算出的时间执行。
5.4 schedule方法下面,重写了run方法。
记录任务实际执行的时间。
调用父类的 run 方法,执行任务的主要逻辑。记录任务执行完成的时间。
使用 triggerContextMonitor 对象进行同步,确保多线程环境下的安全访问。
this.triggerContext.update(this.scheduledExecutionTime, actualExecutionTime, completionTime);更新触发器上下文 (triggerContext),将预定执行时间、实际执行时间和完成时间传入,以便后续触发器的计算和任务状态的更新。
if (!obtainCurrentFuture().isCancelled()) { schedule(); }:如果当前任务的 ScheduledFuture 对象未被取消,则重新调度任务,即再次调用 schedule() 方法安排下一次执行。
这个方法的作用是在任务执行完成后,更新触发器上下文,并根据当前任务的状态决定是否重新安排下一次执行。
5.5 父类的 run 方法
这个类的作用是提供一个可以处理错误的 Runnable 包装类,通过在 run 方法中捕获异常并调用错误处理器来处理异常,从而保证任务执行过程中的异常不会导致整个程序崩溃,并且可以在异常发生时进行相应的处理。
我们在实际实现过程中,可以自定义自己的Runnable 实现方法。
public class MyRunnable implements Runnable {
private String message;
public MyRunnable(String message) {
this.message = message;
}
@Override
public void run() {
System.out.println("Executing MyRunnable with message: " + message);
// 添加自己的逻辑代码
}
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable("Hello, world!");
Thread thread = new Thread(myRunnable);
thread.start();
}
}
6 自定义任务调度器实现全流程demo(仅作参考)
@Configuration
@EnableScheduling
public class MySchedulingConfigurer implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(taskExecutor());
taskRegistrar.addTriggerTask(
new MyRunnable("Hello, world!"),
triggerContext -> {
// 定义触发器,每隔一段时间执行一次
CronTrigger cronTrigger = new CronTrigger("0/5 * * * * ?");
return cronTrigger.nextExecutionTime(triggerContext);
}
);
}
@Bean(destroyMethod="shutdown")
public ThreadPoolTaskScheduler taskExecutor() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(10);
return scheduler;
}
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(MySchedulingConfigurer.class);
context.refresh();
}
}
看了一下午spring源码,轮子造起来!!!