前言
在上一章quartz的使用-CSDN博客有讲关于quartz的使用,包括常规使用,结合mysql存储做分布式调度和结合springboot使用,本篇文章主要做定时调度的主流程源码分析
定式
public static void main(String[] args) throws Exception{
SchedulerFactory factory = new StdSchedulerFactory();
Scheduler scheduler = factory.getScheduler();
JobKey jobKey = new JobKey("triggerTX2", "groupTX1"); // 任务(Job)的唯一标识,如果在创建任务时没有设置该项,由框架生成
List<? extends Trigger> triggersOfJob = scheduler.getTriggersOfJob(jobKey);
if (Objects.nonNull(triggersOfJob) && triggersOfJob.size() > 0) {
scheduler.resumeJob(jobKey);
} else {
JobDetail jobDetail = JobBuilder.newJob(FirstJob.class)
.withIdentity(jobKey)
.usingJobData("userName", "zhangsan") // 此处可以添加需要的调度参数,然后会把参数封装到JobExecutionContent中
.build();
CronTrigger trigger = TriggerBuilder
.newTrigger()
.withIdentity(jobKey.getName(),jobKey.getGroup()) // 设置标识
.usingJobData("address","123") // 此处可以添加需要的调度参数,然后会把参数封装到JobExecutionContent中
.withSchedule(CronScheduleBuilder.cronSchedule("0 0/1 * * * ?"))
.startNow()
.build();
scheduler.scheduleJob(jobDetail,trigger);
}
scheduler.start();
}
重要的类
Quartz框架中重要的类和作用分别简介如下:
SchedulerFactory
调度器工厂,顾名思义该类是用于生产调度器的,常用的实现是:StdSchedulerFactory 标准调度器工厂
Scheduler
最常用的实现是 org.quartz.impl.StdScheduler 标准调度器,此类持有一个QuartzScheduler实例
Trigger
触发器,用于触发任务的调用,常用的实现是:org.quartz.impl.triggers.CronTriggerImpl,该类包含了触发调度需要的cron表达式信息,和当前触发器的下次,上次调度时间等信息
JobDetail
具体的任务信息,最常用的实现是:org.quartz.impl.JobDetailImpl 该类包含了该任务关联的执行类,该任务的名称,所属任务组,任务执行过程中所需的参数信息等
QuartzScheduler
quartz框架最核心的调度单元,持有参与调度的资源信息(QuartzSchedulerResources实例),调度线程信息(QuartzSchedulerThread实例),调度上下文信息等等
QuartzSchedulerResources
调度的资源信息,包括quartz.properties配置文件中设置的属性和其他的信息,是quartz的仓库类
QuartzSchedulerThread
quartz框架发起调度的入口,本质是一个线程。是触发调度的入口
JobRunShell
本质是一个runnable接口实例,真正的任务执行单元,持有JobDetail详细信息(间接持有)
从上图可知:JobRunShell间接持有JobDetail,也就是持有了任务执行的类相关信息,后续的执行就是通过JobRunShell执行的,详细的执行逻辑:org.quartz.core.JobRunShell#run
ThreadPool
本质是一个线程池,通过该线程池调度JobRunShell实例,实现任务的调度,常见的实现是
org.quartz.simpl.SimpleThreadPool。该类是发起JobRunShell执行的入口,其中有3个重要的列表
其中workers是线程总数,availworkers是可用的线程列表,也就是空闲的线程列表,busyWorkers是正在执行任务的线程列表
入口分析
在上面已经对quartz框架的重要的类和作用做了简单的介绍。这次从定式触发,从入口分析定式调度的整个过程
Scheduler scheduler = factory.getScheduler();
factory的实现类是StdSchedulerFactory,真正的执行逻辑是:
public Scheduler getScheduler() throws SchedulerException {
if (cfg == null) {
initialize();
}
SchedulerRepository schedRep = SchedulerRepository.getInstance();
Scheduler sched = schedRep.lookup(getSchedulerName());
if (sched != null) {
if (sched.isShutdown()) {
schedRep.remove(getSchedulerName());
} else {
return sched;
}
}
sched = instantiate();
return sched;
}
第一步:读取配置文件
其中cfg(PropertiesParser)是一个配置类。通过读取配置文件初始化信息,配置文件的读取顺序是:
1 首先读取系统参数org.quartz.properties配置文件位置
2 如果1未读取到配置文件,读取项目resources目录下的quartz.properties文件
3 如果2位读取到配置文件,直接读取框架的配置文件信息
具体信息参见:org.quartz.impl.StdSchedulerFactory#initialize()
第二步:创建调度仓库实例
SchedulerRepository可以简单理解成一个Map,实际上也确实是一个Map
第三部:创建调度器
这部分代码较长,但是逻辑还是比较简单,以下只是简单的列举部分代码做说明
1 通过配置文件获取对应的配置属性,如果未配置,使用默认的配置属性
2 构建 QuartzSchedulerResources
3 构建ThreadPool并调用初始化方法
ThreadPool是发起任务调度的线程池,里面有三个线程列表
初始化方法通过配置参数,构建线程池包含的总线程数并调用
而WorkerThread的核心逻辑受两个参数影响:
其中runnable就是需要运行的线程:
也就是:ThreadPool初始化了一组线程并启动了该组线程,该组线程的核心执行逻辑受2个参数的影响:当runnable==null并且run的值是true时,就阻塞循环。
换言之:如果设置WorkerThread实例中runnable的值,就可以跳出阻塞循环,并执行逻辑!
4 构建JobRunShellFactory
顾名思义,JobRunShellFactory是生产JobRunShell的,而JobRunShell在上面已经简单介绍过,是真正调用任务类核心逻辑的,本质是一个Runnable对象。这里具体是JTAJobRunShellFactory还是JTAAnnotationAwareJobRunShellFactory实现是根据配置:
org.quartz.scheduler.wrapJobExecutionInUserTransaction而定
5 构建QuartzScheduler
注意,上面已经介绍过QuartzScheduler是最核心的调度单元,里面有QuartzSchedulerThread等核心属性,这些属性都在构造方法中体现
而QuartzSchedulerThread就是执行调度的入口
quartzSchedulerThread的核心调用逻辑也受2个参数的控制:paused和halted
QuartzSchedulerThread的业务逻辑如下:查询可用的线程数和触发时间在idlewaitTime以内的触发器列表
然后通过触发器列表找到对应的任务信息
构建JobRunShell并调用初始化方法
通过ThreadPool执行JobRunShell
实际执行ThreadPool#run(Runnable)方法
这里设置WorkerThread的runnable的值。在上面关于WorkerThread的分析中,有讲到WorkerThread的业务逻辑执行当runnable==null并且run的值是true时,就阻塞循环。这里设置了值,就跳出了阻塞循环,执行核心逻辑。
6 构建框架执行所需的其他组件
包括多个监听器的添加等等逻辑
至此:Quartz的核心逻辑已基本梳理清楚,总结如下:
在构建StdScheduler的过程中,构建了ThreadPool,ThreadPool是一组WorkerThread,WokerThread的核心逻辑是通过runnable==null并且run的值是true时控制业务逻辑的执行。通过构建了QuartzSchedulerThread,而QuartzSchedulerThread的核心逻辑就是:查询最近要触发的触发器列表,通过触发器列表找到任务信息,构建RunJobShell。再通过WorkerThread执行RunJobShell,也就是执行任务的实际业务逻辑
疑问解答
scheduler#start方法做了什么
在上面的讲解中,有提到QuartzSchedulerThread的运行是通过两个属性控制:paused和halted
而scheduler.start()即是修改了paused的值,触发QuartSchedulerThread的核心逻辑调用,从而触发整个调度逻辑
scheduler#scheduleJob做了什么?
源码显示,该方法就是简单的对Job信息和trigger信息做了简单的存储和通过监听器发送消息