明天开始学习企业级作业调度框架Quartz。
先简略阐明一下,Quartz的应用阐明文档能够从官网获取:Quartz tutorials,而且中文文档也比拟全面、也不难找到。如果想要练习或者学习如何应用Quartz,参考这类文档很容易上手。
咱们会从另外一个角度学习并记录Quartz的学习过程:尽可能从源码的角度,理解Quartz的底层原理。
作为企业级作业调度框架,复杂程度当然和JDK Timer不在一个数量级,对Quartz的学习也不太容易欲速不达,所以,做好筹备,一步一步来。
一个Quartz的例子
只管Quartz的底层原理比较复杂,然而应用起来也不算简单。
第一步:创立Job对象,实现execute办法。
第二步:创立JobDetail。
第三步:创立Trigger。
第四步:创立Scheduler,将JobDetail对象和Trigger对象绑定到Scheduler中,启动Schedule
@Slf4jpublic 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