Quartz 2 定時任務(二):多線程併發執行與數據共享

版權聲明:本文由吳仙傑創做整理,轉載請註明出處:http://www.javashuo.com/article/p-tqlbjvja-ms.htmlhtml

1. 禁止同一個 JobDetail 中的多個實例併發執行

Quartz 定時任務默認都是併發執行的,不會等待上一次任務執行完畢,只要間隔時間到就會執行,若是定時任執行太長,會長時間佔用資源,致使其它任務堵塞。java

禁止併發執行的意思並非不能同時執行多個 Job,而是不能併發執行同一個 Job Definition(由 JobDetail 定義),可是能夠同時執行多個不一樣的 JobDetail,舉例說明,咱們有一個 Job 類,叫作 SayHelloJob,並在這個 Job 上加了 @DisallowConcurrentExecution 註解,而後在這個 Job上 定義了不少個 JobDetail,如 sayHelloToJoeJobDetail,sayHelloToMikeJobDetail,那麼當 scheduler 啓動時,不會併發執行多個 sayHelloToJoeJobDetail 或者 sayHelloToMikeJobDetail,但能夠同時執行 sayHelloToJoeJobDetail 跟 sayHelloToMikeJobDetail。shell

1.1 使用 Spring 整合 Quartz 時

Spring 配置文件中加入:segmentfault

<!-- false 表示等上一個任務執行完後再開啓新的任務 -->
<property name="concurrent" value="false"/>

1.2 Quartz 原生使用時

當不使用 Spring 的時候就須要在 Job 的實現類上加 @DisallowConcurrentExecution 的註解。api

2. 同一個 JobDetail 中多個實例的數據共享

@PersistJobDataAfterExecution 是用在 Job 實現類上,表示一個有狀態的任務,意思是當正常執行完 Job 後,JobDataMap 中的數據應該被改動,以被下一次調用時用。併發

注意:當使用 @PersistJobDataAfterExecution 註解時,爲了不併發時,存儲數據形成混亂,強烈建議@DisallowConcurrentExecution 註解也加上。ui

3. 示例

假設定時任務的時間間隔爲 3 秒,但 job 執行時間是 10 秒。當設置 @DisallowConcurrentExecution 之後程序會等任務執行完畢後再去執行,不然會在 3 秒時再啓動新的線程執行。.net

當設置 @PersistJobDataAfterExecution 時,在執行完 Job 的 execution 方法後保存 JobDataMap 當中固定數據,以便任務在重複執行的時候具備相同的 JobDataMap;在默認狀況下也就是沒有設置 @PersistJobDataAfterExecution 的時候每一個 job 都擁有獨立 JobDataMap。線程

任務類:code

package org.quartz.examples;

import org.quartz.*;

import java.util.Date;

@PersistJobDataAfterExecution
@DisallowConcurrentExecution
public class TaskJob implements Job {

    public static final String NUM_EXECUTIONS = "NumExecutions";
    public static final String EXECUTION_DELAY = "ExecutionDelay";

    /**
     * 靜態變量能夠保持工做狀態,但沒法達到預期效果
     */
    private static int _staticCounter = 0;

    /**
     * Quartz 每次執行做業時都會從新實例化,非靜態變量沒法保持工做狀態
     */
    private int _counter = 0;

    /**
     * 須要一個公共的空構造方法,以便 scheduler 隨時實例化 job
     */
    public TaskJob() {
    }

    /**
     * 該方法實現須要執行的任務
     */
    public void execute(JobExecutionContext context) throws JobExecutionException {
        System.err.println("---> " + context.getJobDetail().getKey() + " 運行中[" + new Date() + "]");

        JobDataMap map = context.getJobDetail().getJobDataMap();

        int executeCount = 0;
        if (map.containsKey(NUM_EXECUTIONS)) {
            executeCount = map.getInt(NUM_EXECUTIONS);
        }

        // 增量計數並將其存儲回 JobDataMap,這樣能夠適當保持工做狀態
        executeCount++;
        map.put(NUM_EXECUTIONS, executeCount);

        // 只要有任務執行都會遞增,沒法達到預期效果
        _staticCounter++;

        // 本地變量遞增長,但實際上沒法保持工做狀態
        _counter++;

        long delay = 5000L;
        if (map.containsKey(EXECUTION_DELAY)) {
            delay = map.getLong(EXECUTION_DELAY);
        }

        try {
            // 模擬一個耗時的 job
            Thread.sleep(delay);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.err.println(context.getJobDetail().getKey() + " 的靜態變量 _staticCounter 爲:" + _staticCounter + ",非靜態變量 scheduler 爲:" + _counter);
        System.err.println(context.getJobDetail().getKey() + " 完成了(" + executeCount + ")次 <---");
    }
}

任務調度類:

package org.quartz.examples;

import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;

import java.util.Date;

public class Executer {

    public void run() throws Exception {
        // 經過 schedulerFactory 獲取一個調度器
        SchedulerFactory sf = new StdSchedulerFactory();
        Scheduler sched = sf.getScheduler();

        // 建立 jobDetail 實例,綁定 Job 實現類
        // 指明 job 的名稱,所在組的名稱,以及綁定 job 類
        JobDetail job1 = JobBuilder.newJob(TaskJob.class)
                .withIdentity("statefulJob1", "group1")
                // 給定的鍵-值對添加到 JobDetail 的 JobDataMap 中
                .usingJobData(TaskJob.EXECUTION_DELAY, 10000L).build();

        // 定義調度觸發規則,先當即執行一次,而後每隔 3 秒執行一次
        SimpleTrigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("trigger1", "group1")
                .startNow()
                .withSchedule(SimpleScheduleBuilder.simpleSchedule()
                        .withIntervalInSeconds(3)
                        .repeatForever())
                .build();

        // 把做業和觸發器註冊到任務調度中
        Date firstRunTime = sched.scheduleJob(job1, trigger);
        System.out.println(job1.getKey() + " 開始運行於:" + firstRunTime + ",重複:" + trigger.getRepeatCount() + " 次,每次間隔 "
                + trigger.getRepeatInterval() / 1000 + " 秒");

        // 任務 job1 方法中拿到的 JobDataMap 的數據是共享的
        // 這裏要注意一個狀況: 就是 JobDataMap 的數據共享只針對一個 job1 任務
        // 若是在下面在新增長一個任務 那麼他們之間是不共享的,好比下面的 job2
        // 建立第二個 JobDetail 實例
        JobDetail job2 = JobBuilder.newJob(TaskJob.class)
                .withIdentity("statefulJob2", "group1")
                // 給定的鍵-值對添加到 JobDetail 的 JobDataMap 中
                .usingJobData(TaskJob.EXECUTION_DELAY, 10000L)
                .build();

        // 定義調度觸發規則,先當即執行一次,而後每隔 3 秒執行一次
        trigger = TriggerBuilder.newTrigger()
                .withIdentity("trigger2", "group1")
                .startNow()
                .withSchedule(SimpleScheduleBuilder.simpleSchedule().
                        withIntervalInSeconds(3)
                        .repeatForever()
                        // 指定失效時的策略
                        .withMisfireHandlingInstructionNowWithExistingCount())
                .build();

        // 這個 job2 與 job1 執行的 JobDataMap 不共享
        // 把做業和觸發器註冊到任務調度中
        firstRunTime = sched.scheduleJob(job2, trigger);
        System.out.println(job2.getKey() + " 開始運行於:" + firstRunTime + ",重複:" + trigger.getRepeatCount() + " 次,每次間隔 "
                + trigger.getRepeatInterval() / 1000 + " 秒");

        // 啓動計劃程序(實際上直到調度器已經啓動纔會開始運行)
        sched.start();

        // 等待 60 秒,使咱們的 job 有機會執行
        Thread.sleep(60000);

        // 等待做業執行完成時才關閉調度器
        sched.shutdown(true);

        SchedulerMetaData metaData = sched.getMetaData();
        System.out.println("一共運行了:" + metaData.getNumberOfJobsExecuted() + " 個任務");
    }

    public static void main(String[] args) throws Exception {
        Executer example = new Executer();
        example.run();
    }
}

運行結果:

group1.statefulJob1 開始運行於:Wed Apr 19 17:04:22 CST 2017,重複:-1 次,每次間隔 3 秒
group1.statefulJob2 開始運行於:Wed Apr 19 17:04:22 CST 2017,重複:-1 次,每次間隔 3 秒

---> group1.statefulJob2 運行中[Wed Apr 19 17:04:22 CST 2017]
---> group1.statefulJob1 運行中[Wed Apr 19 17:04:22 CST 2017]
group1.statefulJob2 的靜態變量 _staticCounter 爲:2,非靜態變量 scheduler 爲:1
group1.statefulJob1 的靜態變量 _staticCounter 爲:2,非靜態變量 scheduler 爲:1
group1.statefulJob2 完成了(1)次 <---
group1.statefulJob1 完成了(1)次 <---
---> group1.statefulJob1 運行中[Wed Apr 19 17:04:32 CST 2017]
---> group1.statefulJob2 運行中[Wed Apr 19 17:04:32 CST 2017]
group1.statefulJob1 的靜態變量 _staticCounter 爲:4,非靜態變量 scheduler 爲:1
group1.statefulJob1 完成了(2)次 <---
group1.statefulJob2 的靜態變量 _staticCounter 爲:4,非靜態變量 scheduler 爲:1
group1.statefulJob2 完成了(2)次 <---
---> group1.statefulJob1 運行中[Wed Apr 19 17:04:42 CST 2017]
---> group1.statefulJob2 運行中[Wed Apr 19 17:04:42 CST 2017]
group1.statefulJob2 的靜態變量 _staticCounter 爲:6,非靜態變量 scheduler 爲:1
group1.statefulJob1 的靜態變量 _staticCounter 爲:6,非靜態變量 scheduler 爲:1
group1.statefulJob1 完成了(3)次 <---
group1.statefulJob2 完成了(3)次 <---
---> group1.statefulJob1 運行中[Wed Apr 19 17:04:52 CST 2017]
---> group1.statefulJob2 運行中[Wed Apr 19 17:04:52 CST 2017]
group1.statefulJob2 的靜態變量 _staticCounter 爲:8,非靜態變量 scheduler 爲:1
group1.statefulJob2 完成了(4)次 <---
group1.statefulJob1 的靜態變量 _staticCounter 爲:8,非靜態變量 scheduler 爲:1
group1.statefulJob1 完成了(4)次 <---
---> group1.statefulJob2 運行中[Wed Apr 19 17:05:02 CST 2017]
---> group1.statefulJob1 運行中[Wed Apr 19 17:05:02 CST 2017]
group1.statefulJob2 的靜態變量 _staticCounter 爲:10,非靜態變量 scheduler 爲:1
group1.statefulJob1 的靜態變量 _staticCounter 爲:10,非靜態變量 scheduler 爲:1
group1.statefulJob2 完成了(5)次 <---
group1.statefulJob1 完成了(5)次 <---
---> group1.statefulJob1 運行中[Wed Apr 19 17:05:12 CST 2017]
---> group1.statefulJob2 運行中[Wed Apr 19 17:05:12 CST 2017]

group1.statefulJob2 的靜態變量 _staticCounter 爲:12,非靜態變量 scheduler 爲:1
group1.statefulJob2 完成了(6)次 <---
group1.statefulJob1 的靜態變量 _staticCounter 爲:12,非靜態變量 scheduler 爲:1
group1.statefulJob1 完成了(6)次 <---

一共運行了:12 個任務

4. 參考

PS:本文針對的 Quartz 版本爲 Quartz 2.2.3。官方下載地址:Quartz 2.2.3 .tar.gz

相關文章
相關標籤/搜索