Quartz Job Scheduling Framework[翻译]第四章. 部署 Job (第四部分)

七. 线程在 Quartz 中的用法

线程与 Quartz 来说尤为重要,因为 Quartz  就是设计为支持同时运行多个 Job。为达到此效果,Quartz 非常倚重于内建于 Java 语言的线程,借助于自己的类和借口还有所增强。你已经在本章或前面章节中看到过这方面的例子。

当 Quartz Schduler 首次由某个工厂方法创建时,工厂配置了 Scheduler 会在它的整个生命周期中用到的几个重要的资源。其中一些重要的资源是与线程相关的。

·主处理线程:QuartzSchedulerThread

Quartz 应用第一次运行时,main 线程会启动 Scheduler。QuartzScheduler 被创建并创建一个 org.quartz.core.QuartzSchedulerThread 类的实例。QuartzSchedulerThread 包含有决定何时下一个 Job 将被触发的处理循环。顾名思义,QuartzSchedulerThread 是一个 Java 线程。它作为一个非守护线程运行在正常优先级下。

QuartzSchedulerThread 的主处理循环的职责描述如下:

1. 当 Scheduler 正在运行时:

    A. 检查是否有转换为 standby 模式的请求。

       1. 假如 standby 方法被调用,等待继续的信号

    B. 询问 JobStore 下次要被触发的 Trigger.

       1. 如果没有 Trigger 待触发,等候一小段时间后再次检查

2. 假如有一个可用的 Trigger,等待触发它的确切时间的到来

    D. 时间到了,为 Trigger 获取到 triggerFiredBundle.

    E. 使用 Scheduler 和 triggerFiredBundle 为 Job 创建一个 JobRunShell 实例

    F. 告诉 ThreadPool 可能时运行 JobRunShell.

这个逻辑存在于 QuartzSchedulerThreadrun() 方法中。

·QuartzSchedulerResources

当工厂创建 Scheduler 实例时,它还会传递给 Scheduler 一个 org.quartz.core.QuartzSchedulerResoures 实例。QuartzSchedulerResourecs 除包含以上东西之后,还有一个 ThreadPool 实例,它提供了一个工作者线程池来负责执行 Job。在 Quartz 中,ThreadPool 是由 org.quartz.spi.ThreadPool 接口 (因为 Quartz 是在 JDK 1.5 之前产生的,所以需要自己的 ThreadPool 类确保向后的兼容性,Quartz 仍然用自己的 ThreadPool 替代 Java 的) 表示的,并提供一个名为 org.quartz.simp.SimpleThreadPool 的具体实现类。SimpleThreadPool 有一个固定数目的工作者线程,在加载之后就不再减少或增多。图 4.6 是在框架启动时与线程相关的时序图。

图 4.6. 在 Quartz 启动时创建的几个与线程相关的资源

[看全尺寸图]

QuartzFigure4.6.JPG

·什么是 Quartz 工作者线程?

Quartz 不会在 main 线程中处理你的 Job。如果这么做,会严重降低应用的可扩展性。相应的,Quartz 把线程管理的职责委托给分散的组件。对于一般的 Quartz 设置 (这部分还是很费功夫的),都是用SimpleThreadPool  类处理线程的管理。SimpleThreadPool 创建了一定数量的 WorkerThread 实例来使得 Job 能够在分散的线程中进行处理。WorkerThread 是定义在 SimpleThreadPool 类中的内部类,它实质上就是一个线程。要创建 WorkerThread 的数量以及为他们的优先级是配置在文件 quartz.properties 中并传入工厂的。

QuartzSchedulerThread 请求 ThreadPool 去运行 JobRunShell 实例,ThreadPool 就检查看是否有一个可用的工作者线程。假如所以已配置的工作者线程都是忙的,ThreadPool 就等待直到有一个变为可用。当一个工作者线程是可用的,并且有一个 JobRunShell 等待执行,工作者线程就会调用 JobRunShell 类的 run() 方法。

配置可选择的 ThreadPoolQuartz 框架允许你改变所用的 ThreadPool 实现。替换类必须实现 org.quartz.spi.ThreadPool 接口,但是框架只支持通过在文件中配置的方式改变 ThreadPool 的实现类。例如,你可以使用更为高级的 ThreadPool 实现--随时基于需求改变线程的数量,甚至是从应用服务器中获得工作者线程。对于大多数用户,默认的实现就足够了。
 

·JobRunShell 的 run() 方法

虽然 WorkerThread 是真正的 Java 线程,JobRunShell 类也还是实现了 Runable。那意味着它可以作为一个线程并包含一个 run() 方法。在本章前面讨论过,JobRunShell 的目的是调用 Job 的 execute() 方法。不仅如此,它还要负责通知 Job 和 Trigger 监听器,在运行完成后还得更新此次执行的 Trigger 的信息。

八. 理解 Quartz 的 Trigger

Job 包含了要执行任务的逻辑,但是 Job 对何时该执行却一无所知。这个事情留给了 Trigger。Quartz Trigger 继承了抽象的 org.quartz.Trigger 类。当前,Quartz 有三个可用的 Trigger:

    ·org.quartz.SimpleTrigger

    ·org.quartz.CronTrigger

    ·org.quartz.NthIncludeDayTrigger

还有第四个 Trigger,叫做 UICronTrigger,但是到 Quartz 1.5 就不推荐使用了。它主要是用在 Quartz 的 Web 程序中,而不是用于 Quartz 自身中。[译者注:在 Quartz 1.6 中,UICronTrigger 已被彻底摈弃]

·使用 org.quartz.SimpleTrigger

正如其名所示,SimpleTrigger 对于设置和使用是最为简单的一种 Quartz Trigger。它是为那种需要在特定的日期/时间启动,且以一个可能的间隔时间重复执行 n  次的 Job 所设计的。代码 4.9 提供了一个使用 SimpleTrigger 的例子。

代码 4.9. 使用 SimpleTrigger 部署一个 Job

SimpleTrigger 存在几个变种的构造方法。他们是从无参的版本一路到带全部参数的版本。下面代码版断显示了一个仅带有 trigger 的名字和组的简单构造方法
 

这个 Trigger 会立即执行,而不重复。还有一个构造方法带有多个参数,配置 Triiger 在某一特定时刻触发,重复执行多次,和两次触发间的延迟时间。

·使用 org.quartz.CronTrigger

CronTrigger 允许设定非常复杂的触发时间表。然而有时也许不得不使用两个或多个 SimpleTrigger 来满足你的触发需求,这时候,你仅仅需要一个 CronTrigger 实例就够了。

顾名思义,CronTrigger 是基于 Unix 类似于 cron 的表达式。例如,你也许有一个 Job,要它在星期一和星期五的上午 8:00-9:00 间每五分钟执行一次。假如你试图用 SimpleTrigger 来实现,你或许要为这个 Job 配置多个 Trigger。然而,你可以使用如下的表达式来产生一个遵照这个时间表触发的 Trigger:

"0 0/5 8 ? * MON,FRI"

因为 CronTrigger 内建的如此强的灵活性,也与生俱来可用于创建几乎无所限制的表达式,所以下一章专注于你想知道的关于 CronTrigger 的东西及 cron 表达式。第五章,"CronTrigger 及更多" 也列了一系列的关于如何为特定触发时间表创建的 CronTrigger 的例子。

·使用 org.quartz.NthIncludedDayTrigger

org.quartz.NthIncludedDayTrigger 是 Quartz 开发团队最新加入到框架中的一个 Trigger。它设计用于在每一间隔类型的第几天执行 Job。例如,你要在每个月的 15 号执行开票的 Job,用 NthIncludedDayTrigger 就再合适不过了。Quartz 的 Caldendar 也可与 Trigger 关联以此把周末与节假日考虑进来,并在必要时跳开这些日期。接下来的代码片断描绘了如何创建一个 NthIncludedDayTrigger.


·为一个 Job 使用多个 Trigger

你并未被强制要接受单个 Trigger 每个 Job。如果你需要一个更复杂的触发计划,你可以创建多个 Trigger 并指派它们给同一个 Job。Scheduler 是基于配置在 Job 上的 Trigger 来决定正确的执行计划的。为同一个 JobDetail 使用多个 Trigger 方法片断如下:

每 Trigger 一个 Job

虽然单个 JobDetail 能够支持多个 Trigger,但一个 Trigger 只能被指派给一个 Job。

·Quartz Calendar

不要混淆了 Quartz 的 Calendar 对象与 Java API 的 java.util.Calendar。它们是应用于不同目的不一样的组件。正如你大概所知,Java 的 Calender 对象是通用的日期和时间工具;许多过去由 Java 的 Date 类提供的功能现在加到了 Calendar 类中了。

另一方面,Quartz 的 Calender 专门用于屏闭一个时间区间,使 Trigger 在这个区间中不被触发。例如,让我们假如你是为一个财务机构(如银行)工作。对于银行普遍的都有许多 "银行节日"。假设你不需要(或不想) Job 在那些日子里运行。你可以采用以下几种方法中的一种来实现:

    ·让你的 Job 总是运行。(这会让银行一团糟)

    ·在节假日期间手动停止你的 Job。(需要专门的人来负责做这个事情)

    ·创建不包含这些日子的多个 Trigger。(这对于设置和维护会较耗时的)

    ·设立一个排除这些日子的银行节日的 Calendar。(很轻松的实现)

虽然你可以用其中的一个方法来解决这样的问题,Quartz 的 Calender 却是特意为此而设计的。

·org.quartz.Calender 接口

Quartz 定义了 org.quartz.Calendar 接口,所有的 Quartz Calendar 必须实现它。它包含了几个方法,但是有两个是最重要的:

Calender 排除时间的粒度Calendar 接口方法参数的类型是 Long。这说明 Quartz Calender 能够排除的时间细致毫秒级。你很可能永远都不需要这么细小的位度,因为大部分的 Job 只需要排除特别的日期或许会是小时。然而,假如你真需要排除到毫秒一级的,Calender 能帮你做到。

作为一个 Quartz Job 的创建者和开发者,你可不必去熟悉 Calender 接口。这主要是因为已有的 Calendar (Quartz 自带的) 需要应付的情况就不够多。开箱即用的,Quartz 包括许多的 Calender 实现足以满足你的要求。表 4.1 列出了 Quartz 自带的随时可用的 Calendar。

表 4.1. Quartz 包含了你的应用可用的许多的 Calender 类型
Calender 名称 用法
BaseCalender org.quartz.impl.calendar.BaseCalender 为高级的 Calender 实现了基本的功能,实现了 org.quartz.Calender 接口

WeeklyCalendar

org.quartz.impl.calendar.WeeklyCalendar

排除星期中的一天或多天,例如,可用于排除周末

MonthlyCalendar

org.quartz.impl.calendar.MonthlyCalendar

排除月份中的数天,例如,可用于排除每月的最后一天

AnnualCalendar

org.quartz.impl.calendar.AnnualCalendar

排除年中一天或多天

HolidayCalendar

org.quartz.impl.calendar.HolidayCalendar

特别的用于从 Trigger 中排除节假日

·使用 Quartz 的 Calendar

要使用 Quartz Calendar,你只需简单的实例化,并加入你要排除的日期,然后用 Scheduler 注册它。最后把这个 Calender 实例与你想要使用该 Calender 的每一个 Trigger 实例关联起来。

多个 Job 共同一个 Calendar

你不能仅仅是把 Calendar 加入到 Scheduler 来为所有 Job 安排 Calendar。你需让 Calendar 实例关联到每一个 Trigger。Calender 实例被加到 Scheduler 中后,它只允许由使用中的 JobStore 存储;你必须让 Calender 依附于 Trigger 实例。

代码 4.10 显示的是一个使用 Quartz AnnualCalender 类执行银行节日的例子。

代码 4.10. 使用 AnnualCalender 来排除银行节日

当你运行代码 4.10 中的例子时,除 7 月 4 号之外,你都可以看到 Job 会执行。作为一个留下来给你做的练习,在方法 scheduleJob() 中改变被排除的日期为你当前的日期。假如你再次跑这段代码,你将会看到当前日期被排除了,下次被触发的时间会是明天了。

为什么我们不用 HolidayCalender ?你也许会有所疑惑,为什么我们在上个例子中不选择使用 HolidayCalender 呢?HolidayCalender 类考虑的是年份。因此,如果你希望在后续三年中排除 7 月 4 日,你需要把三年中的每个日期都作为要排除的项。而 AnnualCalender 可简单的为每年设定要排除的日期,也就更容易的应用于这种情况

·创建你自己的 Calender

这最后一节演示了创建你自己的 Calender 类是多么的容易。假定你需要一个 Calender 去排除小时当中的某分钟。例如,假定你需要排除每小时中的前 5 分钟或者后 15 分钟。你能创建一个新的 Calender 来支持这种功能。

我们也许可以使用 CronTrigger

我们也许可以写出一个 Cron 表达式来排除这些时间,但那样就没了创建一个新 Calender 的乐趣了。

代码 4.11 是 HourlyCalender,我们能用它让排除小时中的一些分钟。

代码 4.11. HourlyCalender 能从每小中排除某些分钟

如果你使用 HourlyCalender 去部署 Job,需要你做的事情是设置小时中你希望排除的分钟;由 Calender 和 Scheduler 做剩下的事情。你能看到在代码 4.12 中对 HourlyCalender 的演示。

 代码 4.12. HourlyCalender 基于小时中排除某些分钟来执行

当你运行 4.12 中的代码,你会看到 PrintInfoJob 在被排除的分钟是不被执行,在方法 setMinuteExcluded() 方法中依你要求改变需排除的分钟,来看看新的 Calender 是如何工作的。

本文链接 https://yanbin.blog/quartz-job-scheduling-framework-4-4/, 来自 隔叶黄莺 Yanbin Blog

[版权声明] Creative Commons License 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。

Subscribe
Notify of
guest

13 Comments
Inline Feedbacks
View all comments