Timer與Quartz--Android和Java開發(fā)你都需要了解

前言:關(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)度器工廠類

二、例子

上個代碼可能更容易懂:

  1. 導(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'
    
  1. 首先,創(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");
    }
}
  1. 然后,根據(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();
  1. 接著,創(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();
  1. 最后,創(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)類

  1. 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();
  1. CropTrigger【重點】
  • 基于Cron表達式:
    1. 由7個子表達式組成 的 字符串,描述了時間表的詳細信息
    2. 格式: [秒] [分] [小時] [日] [月] [周] [年]
  • Cron表達式規(guī)則:


    • 符號意義:[, 或者] [- 范圍] [* 每] [/ 每隔] [? 不關(guān)心(與*意義差不多)] [L 最后的一X] [W 距離X號最近的一個工作] [# 第]。
    • Cron表達式例子:
      1. 0 15 10 ? * *:每天10點15分觸發(fā)。
      2. 0 0/5 14 * * ?:每天下午2點到2點59分,每隔5分鐘執(zhí)行一次。
      3. 0 15 10 ? * MON-FRI:周一到周五每天上午10點15分觸發(fā)。
      4. 0 15 10 ? * 7#3:每月第三周的星期六 觸發(fā)。
      5. 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í)行時間的情況。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,763評論 6 539
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,238評論 3 428
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,823評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,604評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,339評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,713評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,712評論 3 445
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,893評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,448評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,201評論 3 357
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,397評論 1 372
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,944評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,631評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,033評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,321評論 1 293
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,128評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,347評論 2 377

推薦閱讀更多精彩內(nèi)容