前言:關(guān)于任務(wù)定時調(diào)度,Android基本用的要么Timer要么Handler配合Timer,而Java后端基本也是Timer;而其實,除了Timer,還有一個更強大的任務(wù)調(diào)度工具----Quartz。
Timer【概述】
Timer就不多說了
- 作用: 在一個子線程中執(zhí)行定時或循環(huán)的任務(wù)。
- 包:
java.util.Timer
- 相關(guān)方法:
Timer.schedule(參數(shù))
參數(shù)
如下:
舉一個簡單的例子:
問:現(xiàn)在有兩個任務(wù),即時任務(wù)A 【執(zhí)行一次/ 1s】和耗時任務(wù)B【執(zhí)行一次/ 500s】,我想先啟動任務(wù)A,5s后啟動任務(wù)B,并且10s后讓兩個任務(wù)都結(jié)束。
答:
Timer timer = new Timer();
timer.schedule(new TimerTask() {// 任務(wù)A
@Override
public void run() {
System.out.println("Timer正在調(diào)度 線程:" + Thread.currentThread().getName() + " 執(zhí)行任務(wù) A 中。。。");
}
}, 0, 1000)
timer.schedule(new TimerTask() {// 任務(wù)B
@Override
public void run() {
System.out.println("Timer正在調(diào)度 線程:" + Thread.currentThread().getName() + " 執(zhí)行任務(wù) B 中。。。");
try {
System.out.println("任務(wù)B很慢,它讓Timer單例線程睡眠3s");
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, 5000, 500);
try {
System.out.println("主線程睡眠10s");
Thread.sleep(10000);
timer.cancel();
System.out.println("主線程關(guān)閉了Timer的所有任務(wù)");
} catch (InterruptedException e) {
e.printStackTrace();
}
Quartz【重點】
Quartz,一句話: 他就是Timer 的升級版
github地址: https://github.com/quartz-scheduler/quartz
Quartz 總體架構(gòu)
一、最主要的接口和類
- 接口
-
Job
:任務(wù)。 -
JobDetail
: 任務(wù)詳情(extends Serializable, Cloneable)。 -
Trigger
: 觸發(fā)器(extends Serializable, Cloneable, Comparable<Trigger>)。 -
SchedulerFactory
:調(diào)度器工廠。 -
Scheduler
: 調(diào)度器。
-
- 類
-
JobBuilder
:任務(wù)詳情構(gòu)造器。 -
TriggerBuilder
:觸發(fā)器構(gòu)造器。 -
StdSchedulerFactory
:具體的調(diào)度器工廠。 -
SimpleTrigger
:Trigger實現(xiàn)類,和Timer實現(xiàn)功能差不多。 -
CronTrigger
:Trigger實現(xiàn)類,也是Quartz拉開Timer的地方。 -
StdSchedulerFactory
:標準的調(diào)度器工廠類
-
二、例子
上個代碼可能更容易懂:
- 導(dǎo)入Quartz 包:
- Maven:
<dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>2.3.0</version> </dependency>
- Gradle:
compile 'org.quartz-scheduler:quartz:2.3.0'
- 首先,創(chuàng)建一個
Job
實現(xiàn)類,名為JobImpl
:
public class JobImpl implements Job{
///獲取key:value 方法二:通過設(shè)置與key同名同屬性的成員變量,并設(shè)置getter和setter方法來實現(xiàn)值獲取
// 在Quartz反射構(gòu)建Job對象實例時,會自動判斷和調(diào)用相應(yīng)的setter方法,傳入key同名的參數(shù)。
private String stringkeyA;
private Double doubleKeyB;
public String getStringkeyA() {
return stringkeyA;
}
public void setStringkeyA(String stringkeyA) {
this.stringkeyA = stringkeyA;
}
public Double getDoubleKeyB() {
return doubleKeyB;
}
public void setDoubleKeyB(Double doubleKeyB) {
this.doubleKeyB = doubleKeyB;
}
public void execute(JobExecutionContext context) throws JobExecutionException {
///do sth
///Job 可以獲取 的 信息如下:
///獲取 JobDetail 對象引用
JobDetail jobDetail = context.getJobDetail();
///獲取job 的 標識名 和 所在組別
JobKey jobKey = jobDetail.getKey();
jobKey.getName();
jobKey.getGroup();
/// 獲取 Trigger 對象引用
Trigger trigger = context.getTrigger();
///獲取trigger 的 標識名 和 所在組別
TriggerKey triggerKey = trigger.getKey();
triggerKey.getName();
triggerKey.getGroup();
// 獲取 JobDetail 中設(shè)定的key:value
JobDataMap jobMap = jobDetail.getJobDataMap();
// 獲取 Trigger 中設(shè)定的key:value
JobDataMap triggerMap = trigger.getJobDataMap();
/// 合并并獲取 JobDetail 和 Trigger 對象上設(shè)置的 key:value
JobDataMap map = context.getMergedJobDataMap();
///獲取key:value 方法一:通過Map中獲取
String stringKeyA = map.getString("stringKeyA");
String doubleKeyA = map.getString("doubleKeyA");
}
}
- 然后,根據(jù)這個
JobImpl
和一些任務(wù)參數(shù)配置,構(gòu)造一個專門做JobImpl
這類任務(wù)的JobDetail
對象:
//創(chuàng)建一個JobDetail ,并讓其與 JobImpl 綁定起來
JobDetail jobDetail = JobBuilder
.newJob(JobImpl.class)
.withIdentity("JobA", "group1")
.usingJobData("stringkeyA","任務(wù)DataA")
.usingJobData("doublekeyB",2.15D)
.build();
- 接著,創(chuàng)建一個Trigger對象,用于待會被調(diào)度器觸發(fā),Trigger用于設(shè)置任務(wù)的觸發(fā)時間以及觸發(fā)周期等。
//創(chuàng)建一個Trigger實例,并定義該job立即執(zhí)行,每隔2s重復(fù)執(zhí)行一次,直到永遠
Trigger trigger = TriggerBuilder.newTrigger().withIdentity("triggerA", "group1")
.startNow()
.withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(2).repeatForever())
.usingJobData("stringkeyA","Trigger DataB")
.usingJobData("floatkeyC",3.1f)
.build();
- 最后,創(chuàng)建一個調(diào)度器,讓其開始跑任務(wù)。
SchedulerFactory factory = new StdSchedulerFactory();///標準工廠
Scheduler scheduler = factory.getScheduler();///構(gòu)建 調(diào)度器 實例
scheduler.scheduleJob(jobDetail,cronTrigger);// 讓調(diào)度器 綁定 任務(wù)詳情 和 觸發(fā)器
scheduler.start();//執(zhí)行任務(wù)
//........
scheduler.standby();//我想暫時掛起調(diào)度器,不讓其執(zhí)行任務(wù)
//........
scheduler.start();/// 重啟調(diào)度器
//........
scheduler.shutdown(true);//終止調(diào)度器(true:等所有job都跑完,才終止;false:不等job,直接終止)
三、關(guān)于Trigger實現(xiàn)類
- SimpleTrigger
//以上的Trigger 實際上默認就是一個SimpleTrigger
Date startTime = new Date(System.currentTimeMillis());
Date endTime = new Date(System.currentTimeMillis()+3000);
SimpleTrigger simpleTrigger = (SimpleTrigger) TriggerBuilder.newTrigger()
.withIdentity("mySimpleTrigger","G1")
.startAt(startTime)
.endAt(endTime)
.withSchedule(SimpleScheduleBuilder
.simpleSchedule()
.withRepeatCount(3)//重復(fù)次數(shù):0,正整數(shù),或無窮
.withIntervalInSeconds(2))// 執(zhí)行周期: 0,長整數(shù)
.build();
- CropTrigger【重點】
- 基于Cron表達式:
- 由7個子表達式組成 的 字符串,描述了時間表的詳細信息
- 格式:
[秒] [分] [小時] [日] [月] [周] [年]
-
Cron表達式規(guī)則:
- 符號意義:[
,
或者] [-
范圍] [*
每] [/
每隔] [?
不關(guān)心(與*
意義差不多)] [L
最后的一X] [W
距離X號最近的一個工作] [#
第]。 - Cron表達式例子:
-
0 15 10 ? * *
:每天10點15分觸發(fā)。 -
0 0/5 14 * * ?
:每天下午2點到2點59分,每隔5分鐘執(zhí)行一次。 -
0 15 10 ? * MON-FRI
:周一到周五每天上午10點15分觸發(fā)。 -
0 15 10 ? * 7#3
:每月第三周的星期六 觸發(fā)。 -
0 15 10 ? * 6L 2016-2017
:從2016年到2017年每個月最后一周的周五觸發(fā)。
-
- 符號意義:[
- CropTrigger例子:
///CronTrigger 基于日歷的調(diào)度(功能強大, 可定義每周周幾 執(zhí)行一次任務(wù) 之類的) /** * Cron表達式: * 由7個子表達式組成 的 字符串,描述了時間表的詳細信息 * 格式: [秒] [分] [小] [時] [日] [月] [周] [年] */
CronTrigger cronTrigger = TriggerBuilder
.newTrigger()
.withIdentity("cronTrigger", "G1")
.withSchedule(
CronScheduleBuilder.cronSchedule("* * * * * ? *")///每一秒執(zhí)行一次
)
.build();
### 四、注意點
1. **`ExecutionContext`在構(gòu)建任務(wù)調(diào)度器的時候,就會被引入到`Job`中。**
2. **`ExecutionContext`本身擁有`JobDetail` 和`Trigger`的引用,這樣,就可以給`Job`的執(zhí)行過程中,隨時獲取到`JobDetail`和`Trigger`的信息。**
3. **`JobDataMap`是`ExecutionContext`攜帶的重要參數(shù),它攜帶了`JobDetail`和`Trigger`在構(gòu)造時設(shè)置的一些基本數(shù)據(jù)類型的參數(shù)。**
4. **`Job`獲取`JobDataMap`中的參數(shù)的方式**【上述例子中有注釋】
* 法一:直接從Map對象中獲取的方式
```
JobDataMap jMap = jobExecutionContext.getJobDetail().getJobDataMap();
JobDataMap tMap = jobExecutionContext.getTrigger().getJobDataMap();
JobDataMap mergeMap = jobExecutionContext.getMergedJobDataMap();
```
> 注意:上述`mergeMap`會將`Trigger`中設(shè)定的屬性,覆蓋掉`JobDetail`中設(shè)定的同名屬性。
* 法二:自定義Job子類中事先定義幾個成員屬性并實現(xiàn)其getter和setter方法,在構(gòu)建Job實例時,會自動調(diào)用Job子類的setter方法,將key同名的value賦值到Job實例中。
5. **關(guān)于quartz.properties文件**
* 它是調(diào)度器參數(shù)的配置文件,包括:
1. 調(diào)度器屬性
2. 線程池屬性
3. 存儲調(diào)度信息
4. 插件屬性配置
* `StdSchedulerFactory`使用`Java.util.Properties`來創(chuàng)建和初始化Quartz調(diào)度器,在創(chuàng)建`Scheduler`對象時,就是根據(jù)`quartz.properties`文件中配置信息。
* Quartz 默認情況下,會讀取project下的`quartz.properties`文件,找不到文件,則會去查找引入的jar包下的`quartz.properties`文件,此文件擁有Quartz的一些相關(guān)配置信息。
* 用法:
1. 一般情況下,將`Jar包目錄/org.quartz/`下的`quartz.properties`文件copy 到你的項目根目錄下。
2. 然后,可以自行配置各個參數(shù)。【具體配置可以查看[Quartz官網(wǎng)](http://www.quartz-scheduler.org/)】
# 總結(jié)一波
---
?實際上, Timer 和 Quartz 有一個共同的理念,就是:將想要在某些時刻執(zhí)行的任務(wù)交給工作線程,并讓該線程通過阻塞等機制,到了某一時刻執(zhí)行。
?只不過,Timer內(nèi)部是一個單例線程和一個數(shù)組類型的隊列結(jié)構(gòu),多個任務(wù)可以入隊等待,但同一時刻只有一個任務(wù)會被執(zhí)行,并沒有并發(fā)這一點,每個任務(wù)在執(zhí)行一次后,如果有循環(huán),那么則會重新入隊。
? 而Quartz ,相對于Timer的優(yōu)勢,總體來說,兩點:
1. 更強的定時調(diào)度能力:Quartz的CronTrigger,擁有Timer無法實現(xiàn)的日歷定時調(diào)度功能。
2. 任務(wù)并發(fā)能力:Quartz內(nèi)部通過線程池管理多個線程,能夠讓任務(wù)并發(fā)執(zhí)行,從而避免了使用Timer時由于前一個耗時任務(wù)導(dǎo)致隊列中后一個任務(wù)錯過執(zhí)行時間的情況。