明天开始学习企业级作业调度框架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包含的要害组件:

  1. Job:工作接口
  2. JobDetail:工作详情接口
  3. Trigger:触发器
  4. Schedule:任务调度器
  5. ScheduleThread:任务调度线程
  6. SimpleThreadPool:工作执行线程池
  7. WorkerThread:工作执行线程
  8. 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接口的实现,顾名思义,是一个简略的线程池的实现,几个重要概念包含:

  1. count:只保护了一个线程数,没有最大线程数、最大流动线程数等概念。
  2. workers:工作线程,初始化的时候会创立count个线程并放在workers中。
  3. availWorkers:可用线程,workers中没有被调度的线程,搁置在availWorkers中。
  4. 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