1.Quartz 體系結(jié)構(gòu)
Quartz 設(shè)計(jì)有四個(gè)核心類右锨,分別是Scheduler(調(diào)度器)瞧筛、Job(任務(wù)) 、Trigger(觸發(fā)器)抹估、JobDetail(任務(wù)詳情),他們是使用Quartz的關(guān)鍵弄兜。
1.1 Job
Job:定義需要執(zhí)行的任務(wù)药蜻,該類是一個(gè)接口瓷式,只定義了一個(gè)方法execute(JobExecutionContext context)
,在實(shí)現(xiàn)類的execute
方法中編寫所需要定時(shí)執(zhí)行的Job(任務(wù)),JobExcutionContext
類提供了調(diào)度應(yīng)用的一些信息语泽。Job運(yùn)行時(shí)的信息保存在JobDataMap實(shí)例中贸典。
1.2 Trigger
Trigger:負(fù)責(zé)設(shè)置調(diào)度策略。該類是一個(gè)接口踱卵,描述觸發(fā)job執(zhí)行的時(shí)間觸發(fā)規(guī)則廊驼。主要有SimpleTrigger和CronTrigger這兩個(gè)子類。當(dāng)且僅當(dāng)需調(diào)度一次或者以固定時(shí)間間隔周期執(zhí)行調(diào)度惋砂,SimpleTrigger 是最適合的選擇妒挎;而CronTrigger則可以通過Cron表達(dá)式定義出各種復(fù)雜時(shí)間規(guī)則的調(diào)度方案:如在周一到周五的15:00 ~ 16:00 執(zhí)行調(diào)度等。
1.3 Scheduler
Scheduler:調(diào)度器就相當(dāng)于一個(gè)容器西饵,裝載著任務(wù)和觸發(fā)器酝掩。該類是一個(gè)接口。代表一個(gè)Quartz的獨(dú)立運(yùn)行容器眷柔。Trigger和JobDetail可以注冊(cè)到Scheduler中期虾,兩者在Scheduler中擁有各自的組及名稱,組及名稱是Scheduler查找定位容器中某一對(duì)象的依據(jù)驯嘱。
1.4 JobDetail
JobDetail:描述Job的實(shí)現(xiàn)類及其它相關(guān)的靜態(tài)信息镶苞,如:Job名字、描述鞠评、關(guān)聯(lián)監(jiān)聽器等信息宾尚。Quartz每次調(diào)度Job時(shí),都重新創(chuàng)建一個(gè)Job實(shí)例谢澈,它接受一個(gè)Job實(shí)現(xiàn)類煌贴,以便運(yùn)行時(shí)通過newInstance()的反射機(jī)制實(shí)例化Job。
1.5 ThreadPool
Scheduler使用一個(gè)線程池作為任務(wù)運(yùn)行的基礎(chǔ)設(shè)施锥忿,任務(wù)通過共享線程池中的線程提高運(yùn)行效率牛郑。
Job有一個(gè) StatefulJob 子接口(Quartz2后用@PersistJobDataAfterExecution注解代替),代表有狀態(tài)的任務(wù)敬鬓,改接口是一個(gè)沒有方法的標(biāo)簽接口淹朋,其目的是讓Quartz知道任務(wù)的類型,以便采用不同的執(zhí)行方案钉答。
無狀態(tài)任務(wù)在執(zhí)行時(shí)擁有自己的 JobDataMap 拷貝础芍,對(duì) JobDataMap 的更改不會(huì)影響下次的執(zhí)行。
有狀態(tài)任務(wù)共享同一個(gè) JobDataMap 實(shí)例数尿,每次任務(wù)執(zhí)行對(duì) JobDataMap 所做的更改會(huì)保存下來仑性,后面的執(zhí)行可以看到這個(gè)更改,也即每次執(zhí)行任務(wù)后都會(huì)對(duì)后面的執(zhí)行發(fā)生影響右蹦。
正因?yàn)檫@個(gè)原因诊杆,無狀態(tài)的Job能并發(fā)執(zhí)行歼捐,而有狀態(tài)的StatefulJob不能并發(fā)執(zhí)行。
1.6 Listener
Listener:Quartz擁有完善的事件和監(jiān)聽體系晨汹,大部分組件都擁有事件豹储,如:JobListener監(jiān)聽任務(wù)執(zhí)行前事件、任務(wù)執(zhí)行后事件淘这;TriggerListener監(jiān)聽觸發(fā)前事件剥扣,出發(fā)后事件;TriggerListener監(jiān)聽調(diào)度開始事件,關(guān)閉事件等等铝穷,可以注冊(cè)響應(yīng)的監(jiān)聽器處理感興趣的事件钠怯。
2.cronExpression 表達(dá)式
格式:[秒][分][時(shí)][每月的第幾日][月][每周的第幾日][年]
字段名 | 必填 | 允許值 | 允許特殊符號(hào) |
---|---|---|---|
Seconds | YES | 0 - 59 |
, - * /
|
Minutes | YES | 0- 59 |
, - * /
|
Hours | YES | 0-23 |
, - * /
|
Day of month | YES | 1-31 |
, - * ? / L W
|
Month | YES | 1-12 or JAN-DEC |
, - * /
|
Day of Week | YES | 1-7 or SUN-SAT |
, - * ? / L #
|
Year | NO | empty, 1970-2099 |
, - * /
|
特殊字符說明:
字段 | 含義 |
---|---|
* |
用于指定字段中所有值 。比如:* 在分鐘中表示每一分鐘
|
? |
用于指定日期中的某一天 氧骤,或是星期中的某一個(gè)星期 。 |
- |
用于指定范圍 吃引。比如:10-12 在小時(shí)中表示 10點(diǎn)筹陵、11點(diǎn)、12點(diǎn) 镊尺。 |
, |
用于指定額外的值 朦佩。比如:MON,WED,FRI 在日期中表示星期一,星期三庐氮,星期五
|
/ |
用于指定增量 语稠。比如:0/15 在秒中表示0秒,15秒弄砍,30秒仙畦,45秒 。5/15 在秒鐘表示 5秒音婶,20秒慨畸,35秒,50秒 衣式。 |
L |
在兩個(gè)字段中擁有不同含義寸士。比如L 在日期(Day of month)表示某月的最后一天 。在星期(Day of week)只表示7 或SAT 碴卧。但是弱卡,值L 在星期(Day of week)中表示某月的最后一個(gè)星期幾 。比如6L 表示某月的最后一個(gè)星期五 住册。也可以在日期中(Day of month)中指定一個(gè)偏移量(從該月最后一天開始)婶博。比如L-3 表示某月的倒數(shù)第三天 。 |
W |
用于指定工作日(星期一到星期五) 比如:15W在日期中表示到15號(hào)的最近一個(gè)工作日 荧飞。如果15號(hào)是周六凡蜻,那么觸發(fā)器的觸發(fā)在14號(hào)星期五 搭综。如果15號(hào)是周日,觸發(fā)器的觸發(fā)在15號(hào)周一 划栓。如果十五號(hào)是星期二兑巾,那么它就會(huì)在15號(hào)周二 開始執(zhí)行。然而 如果指定1W 并且1號(hào)是星期六忠荞,那么觸發(fā)器的觸發(fā)在3號(hào)周一 蒋歌,因?yàn)樗粫?huì)“jump”過一個(gè)月的日子邊界。 |
L 和W
|
可以在日期(day-of-month)合使用委煤,表示月份的最后一個(gè)工作日 堂油。 |
# |
用于指定月份中的第幾天 。比如:6#3 表示月份的第三個(gè)星期五 (day 6 = Friday and "#3" = the 3rd one in the month)碧绞。其它的有府框,2#1 表示月份第一個(gè)星期一 。4#5 表示月份第五個(gè)星期三 讥邻。注意:如果只是指定#5 迫靖,則觸發(fā)器在月份中不會(huì)觸發(fā)。 |
注意:字符不區(qū)分大小寫兴使,MON
,mon
相同系宜。
2.1 cornExpression示例
表達(dá)式 | 含義 |
---|---|
0 0 12 * * ? |
每天12點(diǎn) |
0 15 10 ? * * |
每天上午10點(diǎn) 15 |
0 15 10 * * ? |
每天上午10點(diǎn) 15 |
0 15 10 * * ? * |
每天上午10點(diǎn) 15 |
0 15 10 * * ? 2005 |
在2005年中每天的上午10點(diǎn)15 |
0 * 14 * * ? |
每天14點(diǎn)-14點(diǎn)59分的每分鐘 |
0 0/5 14 * * ? |
每天14點(diǎn) 到14點(diǎn)55 每5分鐘執(zhí)行一次 |
0 0/5 14,18 * * ? |
每天14點(diǎn)到14點(diǎn)55 18點(diǎn)到18點(diǎn)55 每5分鐘執(zhí)行一次 |
0 0-5 14 * * ? |
每天14點(diǎn)到14點(diǎn)5分 的每分鐘 |
0 10,44 14 ? 3 WED |
3月每個(gè)星期三的 14點(diǎn)10分和44分 |
0 15 10 ? * MON-FRI |
每周一到周五的10點(diǎn)15分 |
0 15 10 15 * ? |
每月15號(hào)的10點(diǎn)15分 |
0 15 10 L * ? |
每月的最后一天的10點(diǎn)15分 |
0 15 10 L-2 * ? |
每月最后兩天的10點(diǎn)15分(每月的倒數(shù)第二天的10點(diǎn)15分?发魄?盹牧?(需驗(yàn)證)) |
0 15 10 ? * 6L |
每月的最后一個(gè)周五10點(diǎn)15分 |
0 15 10 ? * 6L 2002-2005 |
02-05年 每月最后一個(gè)周五10點(diǎn)15 |
0 15 10 ? * 6#3 |
每月第三周的周五10點(diǎn)15分 |
0 0 12 1/5 * ? |
每月1號(hào)到31號(hào) 每隔5天 12點(diǎn)0分 |
0 11 11 11 11 ? |
每年11月11日11點(diǎn)11分 |
``
3.Listener示例
3.1注冊(cè)對(duì)特定作業(yè)的JobListener
scheduler.getListenerManager().addJobListener(
new MyJobListener(),KeyMatcher.keyEquals(new JobKey("job1","group1")));
3.2注冊(cè)對(duì)特定組的所有作業(yè)的JobListener
scheduler.getListenManager().addJobListener(
new MyJobListener(),GroupMatcher.jobGroupEquals("group1"));
3.3注冊(cè)對(duì)兩個(gè)特定組的所有作業(yè)的JobListener
scheduler.getListenManager().addJobListener(
new MyJobListener(),OrMatcher.or(
GroupMatcher.jobGroupEquals("group1"),
GroupMatcher.jobGroupEquals("group2")));
3.4 注冊(cè)一個(gè)對(duì)所有作業(yè)的JobListener
sched.getListenerManager().addJobListener(
new MyJobListener(),EverythingMatcher.allJobs());
3.5 JobListener 實(shí)現(xiàn)類
public class MyJobListener implements JobListener {
@Override
public String getName() {
return "MyJobListener"; // 一定要設(shè)置名稱
}
@Override
public void jobToBeExecuted(JobExecutionContext jobExecutionContext) {
}
@Override
public void jobExecutionVetoed(JobExecutionContext jobExecutionContext) {
}
@Override
public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) {
if (jobException != null) {
try {
// 立即關(guān)閉調(diào)度器
context.getScheduler().shutdown();
System.out.println("Error occurs when executing jobs, shut down the scheduler.");
// 給管理員發(fā)送郵件...
} catch (SchedulerException e) {
e.printStackTrace();
}
}
}
}
4.SchedulerListener 示例
SchedulerListener 在調(diào)度程序的Scheduler中注冊(cè)。SchedulerListener 可以運(yùn)行任何實(shí)現(xiàn) org.quartz.SchedulerListener 接口的對(duì)象励幼。
4.1 添加調(diào)度器的 SchedulerListener:
scheduler.getListenerManager().addSchedulerListener(mySchedListener);
4.2 刪除調(diào)度器的 SchedulerListener:
scheduler.getListenerManager().removeSchedulerListener(mySchedListener);
5.多線程并發(fā)執(zhí)行與數(shù)據(jù)共享
5.1禁止同一個(gè)JobDetail中的多個(gè)實(shí)例并發(fā)執(zhí)行
Quartz定時(shí)任務(wù)默認(rèn)都是并發(fā)執(zhí)行的汰寓。不會(huì)等待上一次任務(wù)執(zhí)行完畢,只要間隔時(shí)間到了就會(huì)執(zhí)行苹粟,如果定時(shí)任務(wù)執(zhí)行太長(zhǎng)踩寇,會(huì)長(zhǎng)時(shí)間占用資源。導(dǎo)致其它任務(wù)堵塞六水。(quartz是用一個(gè)線程池去執(zhí)行的俺孙。線程池有大小。如果同一任務(wù)并發(fā)執(zhí)行過多掷贾。影響線程池其它任務(wù)執(zhí)行)
禁止并發(fā)執(zhí)行的意思并不是不能同時(shí)執(zhí)行多個(gè)Job睛榄,而是不能并發(fā)執(zhí)行同一個(gè)Job Definition(由JobDetail定義),但是可以同時(shí)執(zhí)行多個(gè)不同的JobDetail想帅,舉例說明场靴,我們有一個(gè)Job類,叫做SayHelloJob,并在這個(gè)Job上加了@DisallowConcurrentExecution注解旨剥,然后再這個(gè)Job上定義了很多JobDetail咧欣,如sayHelloTomJobDetail,sayHelloMikeJobDetail轨帜,那么當(dāng)scheduler啟動(dòng)時(shí)魄咕,不會(huì)并發(fā)執(zhí)行多個(gè)sayHelloTomJobDetail 或者sayHelloMikeJobDetail 但可以同時(shí)執(zhí)行 sayHelloTomJobDetail跟sayHelloMikeJobDetail。
5.2 同一個(gè)JobDetail中多個(gè)實(shí)例的數(shù)據(jù)共享
@PersistJobDataAfterExecution 是用在Job實(shí)現(xiàn)類上蚌父,表示一個(gè)有狀態(tài)的任務(wù)哮兰,意思是當(dāng)正常執(zhí)行完Job后,JobDataMap中的數(shù)據(jù)會(huì)保存 給下一次調(diào)用使用苟弛。
注意:當(dāng)使用@persistJobDataAfterExecution注解時(shí)喝滞,為了避免并發(fā)時(shí),存儲(chǔ)數(shù)據(jù)造成混亂膏秫,強(qiáng)烈建議吧@DisallolwConcurrentExecution 注解也加上右遭。
示例
假設(shè)定時(shí)任務(wù)的時(shí)間間隔為 3 秒,但 job 執(zhí)行時(shí)間是 10 秒缤削。當(dāng)設(shè)置 @DisallowConcurrentExecution 以后程序會(huì)等任務(wù)執(zhí)行完畢后再去執(zhí)行窘哈,否則會(huì)在 3 秒時(shí)再啟動(dòng)新的線程執(zhí)行。
當(dāng)設(shè)置 @PersistJobDataAfterExecution 時(shí)僻他,在執(zhí)行完 Job 的 execution 方法后保存 JobDataMap 當(dāng)中固定數(shù)據(jù)宵距,以便任務(wù)在重復(fù)執(zhí)行的時(shí)候具有相同的 JobDataMap腊尚;在默認(rèn)情況下也就是沒有設(shè)置 @PersistJobDataAfterExecution 的時(shí)候每個(gè) job 都擁有獨(dú)立 JobDataMap吨拗。
6.Quartz 異常與中斷
6.1 作業(yè)異常
org.quartz.JobExecutionException
會(huì)在Scheduler(調(diào)度器)運(yùn)行錯(cuò)誤時(shí),由Job(作業(yè))實(shí)現(xiàn)類拋出婿斥。
在我們捕獲異常并解決異常后劝篷,可以調(diào)用JobExecutionException#setRefireImmediately(true) 立即重新執(zhí)行作業(yè)。
假設(shè)我們有一個(gè)會(huì)拋出異常的job實(shí)現(xiàn)類民宿,job實(shí)現(xiàn)類的代碼片段如下
try {
// 一個(gè)異常例子娇妓,假設(shè)第一次傳入的 denominator 為 0,那么將會(huì)拋出異常
calculation = 4815 / denominator;
} catch (Exception e) {
JobExecutionException e2 = new JobExecutionException(e);
// 在第一次異常后活鹰,修改 denominator 參數(shù)為 1哈恰,那么后面的執(zhí)行就不會(huì)出錯(cuò)了
dataMap.put("denominator", "1");
// true 表示立即重新執(zhí)行作業(yè)
e2.setRefireImmediately(true);
throw e2;
}
上面的作樂會(huì)在第一次執(zhí)行時(shí)拋出java.lang.ArithmeticException: / by zero
異常后,馬上又會(huì)執(zhí)行一次志群,之后都可以正常執(zhí)行作業(yè)着绷。
注意:為了共享在同一個(gè)JobDetail中的JobDataMap,我們需要再上面這個(gè)Job實(shí)現(xiàn)類上加入@PersistJobDataAfterExecution 和 @DisallowconcurrentExecution注解锌云。
6.3 捕獲異常荠医,取消所有觸發(fā)器
在我們捕獲異常時(shí),可以調(diào)用JobExecutionException#setUnscheduleAllTriggers(true)取消所有與這個(gè)作業(yè)有關(guān)的觸發(fā)器。
假設(shè)我們有一個(gè)會(huì)爆出一場(chǎng)的job實(shí)現(xiàn)類彬向,job實(shí)現(xiàn)類代碼片段如下:
try {
// 一個(gè)異常例子
int zero = 0;
calculation = 4815 / zero;
} catch (Exception e) {
JobExecutionException e2 = new JobExecutionException(e);
// true 表示 Quartz 會(huì)自動(dòng)取消所有與這個(gè) job 有關(guān)的 trigger兼贡,從而避免再次運(yùn)行 job
e2.setUnscheduleAllTriggers(true);
throw e2;
}
上面的作業(yè)會(huì)在拋出異常后,就不再執(zhí)行任何有關(guān)該作業(yè)的觸發(fā)器了娃胆。
6.3 作業(yè)中斷
org.quartz.InterruptableJob
接口提供了一種中斷機(jī)制遍希,這個(gè)接口只有一個(gè)方法interrupt()
這個(gè)方法會(huì)在用戶發(fā)出中斷請(qǐng)求到Scheduler(調(diào)度器)時(shí)觸發(fā)(即調(diào)用Scheduler#interrupt(JobDetail#getKey())
方法時(shí)觸發(fā))
其中InterruptableJob
接口又繼承了Job
接口,所以當(dāng)我們使用時(shí)缕棵,只需要實(shí)現(xiàn)InterruptableJob
接口孵班,重寫其中的execute
和interrupt
方法即可。