1.前言
上篇文章,我们介绍了quartz的调度过程及关键类。我们知道quartz作为一个调度框架,调度是框架的核心。
由上篇我们知道,在Job被调用之前,经过了几个过程,调度器启动,调度器将job和trigger加入调度器。之后就发现作业被调度了。这里再思考一下调度背后是什么?如果让我们去实现一个调度该怎么做。
2.正文
上文中Scheduler是通过Spring注入的,我们没法了解Scheduler的初始化过程。所以,我们找了个官方示例,示例中可以看到在Job被调度之前有如下3行代码:
SchedulerFactory schedFact = new org.quartz.impl.StdSchedulerFactory();
Scheduler sched = schedFact.getScheduler();
sched.start();
第一行,源码中StdSchedulerFactory的无参构造方法是个空方法,不管他。
第二行,schedFact.getScheduler()方法非常关键,该方法中初始化了QuartzScheduler,并返回了Scheduler。
第三行,调度器调用start()方法,开始启动。
2.1 调度准备
多图预警
第一,我们可以看到QuartzScheduler在初始化的时候,初始化并启动了调度线程QuartzSchedulerThread,并且该线程的标识halted = false,paused = true。源码截图如下:
第二,我们看类之间的关系。StdScheduler是Scheduler的实现类,也是QuartzScheduler的代理类。
由此我们可知,Scheduler的start()方法实际是调用了QuartzScheduler的start()方法。而start()方法中将调度线程QuartzSchedulerThread的paused = false。也就是说退出了while循环里嵌套的while,线程开始向下运行。源码截图如下:
以上是quartz调度前的准备工作。
小结:QuartzSchedulerThread线程启动后,run方法开始运行,因为halted = false故进入第一个while循环,因为halted = false,paused = true接着会进入第二个while循环,第二个循环wait-wait-wait......不停的wait,一直等到start()方法将paused设置为false。则退出嵌套的循环,开始往下运行,而往下才开始真正的调度。
2.2 准备完成,开始调度
先看一下run方法全貌:
总体可以分为三块,前后两个同步代码块,中间夹杂了一些代码。第一个同步代码块就是刚才的嵌套while循环,用于等待调度启动,而另外一个功能类似,也是等待。这里我们重点关注的是中间的部分,图片中红框标注的。变量availThreadCount由名称可以知道,是线程池当前可用的线程数量。availThreadCount如果大于0则进入if分支执行,然后contine。这里好理解,就是去确认下当前是否有可用线程,如果有则开始进入调度逻辑。这部分代码挺长从279行到414行,我们截取关键部分看一下:
第一步,查询即将到触发时间的trigger
第二步,判断trigger是否到时间
第三步,标记trigger已触发
第四步,通过线程池执行Job
最后,下一轮while循环。
2.3 小结
调度线程QuartzSchedulerThread进入while的无限循环,查询JobStore中即将到期的trigger,判断trigger是否到达触发时间,如果到达则通过线程池异步执行trigger绑定的Job。
3. 后记
Quartz作为一个调度框架,提供的丰富的特性,也是基于此源码量多也杂。但是作为一个调度框架核心逻辑仍是调度,我们这里通过看源码,略微一窥他调度的背后是什么。
以下是看源码时,觉得比较关键的类
- Scheduler:调度器接口。
- QuartzScheduler:真正调度器的实现,提供了调度器的启停、作业增删等丰富的功能。
- StdScheduler:调度器的官方实现,实现了Scheduler,也是QuartzScheduler的代理类。
- QuartzSchedulerThread:核心调度线程,也就是他马不停蹄的去运行Job。
- ThreadPool:官方的线程池,用于执行Job。
- JobRunShell:持有TriggerFiredBundle,TriggerFiredBundle持有JobDetail。JobRunShell是一个线程的实现,会真正的调用Job的execute(JobExecutionContext context)方法。
如果对你有bangz帮助,点个赞再走吧