明天开始学习企业级作业调度框架 Quartz。
先简略阐明一下,Quartz 的应用阐明文档能够从官网获取:Quartz tutorials,而且中文文档也比拟全面、也不难找到。如果想要练习或者学习如何应用 Quartz,参考这类文档很容易上手。
咱们会从另外一个角度学习并记录 Quartz 的学习过程:尽可能从源码的角度,理解 Quartz 的底层原理。
作为企业级作业调度框架,复杂程度当然和 JDK Timer 不在一个数量级,对 Quartz 的学习也不太容易欲速不达,所以,做好筹备,一步一步来。
一个 Quartz 的例子
只管 Quartz 的底层原理比较复杂,然而应用起来也不算简单。
第一步:创立 Job 对象,实现 execute 办法。
第二步:创立 JobDetail。
第三步:创立 Trigger。
第四步:创立 Scheduler,将 JobDetail 对象和 Trigger 对象绑定到 Scheduler 中,启动 Schedule
@Slf4j
public class HelloJob implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {log.info("I dont know want should i do ..."+Thread.currentThread().getId());
}
public static void main(String[] args) {JobDetail jobDetail = newJob(HelloJob.class)
.withDescription("This is my first quartz job")
.withIdentity("MyJob")
.build();
Trigger trigger = newTrigger()
.withIdentity("myTriggger","MyGroup")
.startNow()
.withSchedule(simpleSchedule()
.withIntervalInSeconds(1)
.repeatForever())
.build();
try {Scheduler sche = new StdSchedulerFactory().getScheduler();
sche.scheduleJob(jobDetail,trigger);
sche.start();}catch(Exception e){e.printStackTrace();
}
}
运行后果:工作立刻开始执行,每秒钟运行一次,合乎预期。
从执行后果还能够发现,前 10 次工作每次运行时的线程 id 都不一样,第 11 次开始,当前的工作反复前 10 次的线程 id。
Quartz 蕴含的次要对象
Quartz 包含的要害组件:
- Job:工作接口
- JobDetail:工作详情接口
- Trigger:触发器
- Schedule:任务调度器
- ScheduleThread:任务调度线程
- SimpleThreadPool:工作执行线程池
- WorkerThread:工作执行线程
- Job Store:工作存储
Job&JobDetail
Job 是工作接口,蕴含一个 execute 办法。Job 与 JDK Timer 中的 TimerTask 相似,是提供给利用实现工作逻辑的 API。
Quartz 还提供了一个 JobDetail 接口,最终绑定到任务调度器中的不是 Job 而是 JobDetail,稍后咱们会简略剖析下这么做的起因。
JobDetail 不须要利用去实现,Quartz 提供了一个实现类 JobDetailImpl,利用通过 JoBuilder 创立进去的 JobDetail 其实就是这个 JobDetailImpl,JobDetailImpl 会持有利用实现的 Job 实现类的类名,而不是间接持有 Job 实现类的对象。
Trigger
触发器,有点相似 JDK Timer 中的 Timer 对象,但又不齐全一样,为了更大的灵活性,其实 Quartz 相当于把 JDK Timer 中的 Timer 对象分拆成 Trigger 和 Schedule 两个对象。
Triggle 是负责设置工作触发规定的,有两个最根本的实现 SimpleTriggleImpl 和 CronTriggerImpl,SimpleTriggleImpl 实现比较简单的触发规定,CronTriggerImpl 能够反对 cron 表达式,所以能够设置比较复杂的工作触发规定。
Schedule
Schedule 是任务调度器,这部分应该是 Quartz 中比较复杂的局部。
任务调度器通过 StdSchedulerFactory 创立,默认实现是 StdScheduler。
StdScheduler 是代理模式的设计,将所有办法调用委托给他的属性 QuartzScheduler,最终其实是由 QuartzScheduler 负责任务调度的。
咱们从下面的例子代码中其实能够发现,JobDetail 和 Trigger 都绑定到 Schedule 中了,其实也就是绑定在 QuartzScheduler 中,通过 QuartzSchedulerResources 属性持有。
QuartzScheduler 还有一个重要的属性是 QuartzSchedulerThread,他是真正的任务调度器,在 QuartzScheduler 初始化的过程中创立。
上面咱们要说一下这个 QuartzSchedulerThread。
QuartzSchedulerThread
QuartzSchedulerThread 是 Quartz 的任务调度线程,其实 Quartz 受欢迎的一个要害起因之一就是他的线程管理机制,Quartz 的任务调度线程和工作执行线程是离开的,这样他就能够防止 JDK Timer 的一个缺点:工作执行会影响到工作的调度。
QuartzSchedulerThread 在 scheduleJob 的同时会被启动,也就是咱们下面代码中的 sche.scheduleJob(jobDetail,trigger) 调用实现后,QuartzSchedulerThread 线程就被启动了,调度线程启动后就开始循环期待工作被触发器触发,这部分的代码逻辑咱们前面会详细分析。
明天的配角是 SimpleThreadPool,到当初他还没有呈现呢,咱们要把他引出来。
下面例子中创立 Schedule 的代码:
Scheduler sche = new StdSchedulerFactory().getScheduler();
会调用到 StdSchedulerFactory 的 instantiate() 办法,这个办法特地特地特地长 … 咱们临时不钻研它,咱们只关注和 ThreadPool 无关的局部。
正是在这个简短的办法中会创立 ThreadPool, 默认是 SimpleThreadPool,之后会依照配置实现 ThreadPool 的初始化,并将筹备好的 SimpleThreadPool 送给 QuartzSchedulerResources 持有。
tp.initialize();
而后在任务调度线程 QuartzSchedulerThread 的执行主体中(也就是他的 run 办法中),如果某一工作被触发,零碎查看线程池是否有可用的线程:
int availThreadCount = qsRsrcs.getThreadPool().blockForAvailableThreads();
如果有可用的线程的话,零碎持续各项查看获取到须要执行的工作后,将该工作交给线程池、线程池负责拿到一个可用线程并执行当前任务:
if (qsRsrcs.getThreadPool().runInThread(shell) == false)
所以到当初咱们曾经大略晓得了 Quartz 任务调度的简略逻辑,咱们没有认真钻研工作触发的具体过程,起因是咱们明天的次要工作是 SimpleThreadPool。
好在咱们当初曾经晓得了 Quartz 是在什么中央创立线程池、工作触发的时候是怎么把工作交给线程池的。咱们就从这两个方面动手。
初识 SimpleThreadPool
Quartz 的 SimpleThreadPool 是 ThreadPool 接口的实现,顾名思义,是一个简略的线程池的实现,几个重要概念包含:
- count:只保护了一个线程数,没有最大线程数、最大流动线程数等概念。
- workers:工作线程,初始化的时候会创立 count 个线程并放在 workers 中。
- availWorkers:可用线程,workers 中没有被调度的线程,搁置在 availWorkers 中。
- busyWorkers:当利用须要一个线程去执行工作的时候,从 availWorkers 中获取到一个可用线程(从 availWorkers 中移出)后搁置到 busyWorkers 中。
SimpleThreadPool 初始化
通过 initialize() 办法实现初始化:
调用 createWorkerThreads 创立 count 个 WorkerThread 线程对象搁置在 workers 中。
一一启动新创建的 Thread,同时搁置在 availWorkers 中。
count 能够通过 Quartz.properties 文件设置,假如咱们设置的线程数为 10,则初始化实现之后,线程池中会有 10 个线程被启动,并且都在可用状态(都在 availWorkers 中)。
SimpleThreadPool 执行工作
下面从源码中咱们曾经晓得,SimpleThreadPool 通过 runInThread 办法执行工作。
如果 availWorkers 中没有可用线程的话,挂起期待直到其余工作开释线程。
while ((availWorkers.size() < 1) && !isShutdown) {
try {nextRunnableLock.wait(500);
} catch (InterruptedException ignore) {}}
而后从 availWorkers 中获取一个线程,同时将获取到的线程搁置到 busyWorkers 中,并用该线程执行工作。
SimpleThradPool 执行工作
线程池 availWorkers 和 busyWorkers 中存储的其实是 WorkerThread 对象,所以线程池执行工作调用的其实是 WorkerThread 的 run 办法。
WorkerThread 持有一个 Runnable 成员对象,其实就是咱们须要交给线程池执行的工作,run 办法中一直查看是否有工作交进来,如果没有的话就挂起期待。
如果发现有工作交进来,则调用该 Runnable 的 run 办法, 这个时候其实就能调用到应用层 Job 对象的 execute 办法了,然而具体怎么调用到的咱们还是放到前面剖析。
工作执行实现后将 WorkerThread 的 Runnable 对象清空,而后将线程交还给线程池的 availWorkers 中,并且从 busyWorkers 中移出。
OK!
上一篇 JAVA 定时工作 – JDK Timer