Quartz - SimpleThreadPool

今天开始学习企业级作业调度框架Quartz。

先简单说明一下,Quartz的使用说明文档可以从官网获取:[Quartz tutorials](http://www.quartz-scheduler.org/documentation/2.4.0-SNAPSHOT/tutorials/index.html),而且中文文档也比较全面、也不难找到。如果想要练习或者学习如何使用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包括的关键组件:

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在schedule创建的时候同时启动,也就是:

```

Scheduler sche = new StdSchedulerFactory().getScheduler();

```

StdSchedulerFactory的getScheduler()方法调用初始化方法instantiate():

```

sched = instantiate();

```

初始化方法instantiate()的主要任务是完成任务调度器Scheduler的所有相关属性的初始化,代码特别特别长,在方法最后部分创建QuartzScheduler对象:

```

qs = new QuartzScheduler(rsrcs, idleWaitTime, dbFailureRetry);

qsInited = true;

```

QuartzScheduler的构造器函数中会创建QuartzSchedulerThread,创建之后马上交给ThreadExecutor启动:

```

public QuartzScheduler(QuartzSchedulerResources resources, long idleWaitTime, @Deprecated long dbRetryInterval)

throws SchedulerException {

this.resources = resources;

if (resources.getJobStore() instanceof JobListener) {

addInternalJobListener((JobListener)resources.getJobStore());

}

this.schedThread = new QuartzSchedulerThread(this, resources);

ThreadExecutor schedThreadExecutor = resources.getThreadExecutor();

schedThreadExecutor.execute(this.schedThread);

if (idleWaitTime > 0) {

this.schedThread.setIdleWaitTime(idleWaitTime);

}

//省略代码

```

调用完成后,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](https://segmentfault.com/a/1190000043335784)

下一篇 [Quartz - Job & JobDetail](https://segmentfault.com/a/1190000043374847)

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值