任務調度是指基於給定時間點,給定時間間隔或者給定執行次數自動執行任務。本文由淺入深介紹四種任務調度的 Java 實現:java
相信你們都已經很是熟悉 java.util.Timer 了,它是最簡單的一種實現任務調度的方法,下面給出一個具體的例子:linux
package com.ibm.scheduler; import java.util.Timer; import java.util.TimerTask; public class TimerTest extends TimerTask { private String jobName = ""; public TimerTest(String jobName) { super(); this.jobName = jobName; } @Override public void run() { System.out.println("execute " + jobName); } public static void main(String[] args) { Timer timer = new Timer(); long delay1 = 1 * 1000; long period1 = 1000; // 從如今開始 1 秒鐘以後,每隔 1 秒鐘執行一次 job1 timer.schedule(new TimerTest("job1"), delay1, period1); long delay2 = 2 * 1000; long period2 = 2000; // 從如今開始 2 秒鐘以後,每隔 2 秒鐘執行一次 job2 timer.schedule(new TimerTest("job2"), delay2, period2); } } Output: execute job1 execute job1 execute job2 execute job1 execute job1 execute job2
使用 Timer 實現任務調度的核心類是 Timer 和 TimerTask。其中 Timer 負責設定 TimerTask 的起始與間隔執行時間。使用者只須要建立一個 TimerTask 的繼承類,實現本身的 run 方法,而後將其丟給 Timer 去執行便可。web
Timer 的設計核心是一個 TaskList 和一個 TaskThread。Timer 將接收到的任務丟到本身的 TaskList 中,TaskList 按照 Task 的最初執行時間進行排序。TimerThread 在建立 Timer 時會啓動成爲一個守護線程。這個線程會輪詢全部任務,找到一個最近要執行的任務,而後休眠,當到達最近要執行任務的開始時間點,TimerThread 被喚醒並執行該任務。以後 TimerThread 更新最近一個要執行的任務,繼續休眠。sql
Timer 的優勢在於簡單易用,但因爲全部任務都是由同一個線程來調度,所以全部任務都是串行執行的,同一時間只能有一個任務在執行,前一個任務的延遲或異常都將會影響到以後的任務。數據庫
回頁首服務器
鑑於 Timer 的上述缺陷,Java 5 推出了基於線程池設計的 ScheduledExecutor。其設計思想是,每個被調度的任務都會由線程池中一個線程去執行,所以任務是併發執行的,相互之間不會受到干擾。須要注意的是,只有當任務的執行時間到來時,ScheduedExecutor 纔會真正啓動一個線程,其他時間 ScheduledExecutor 都是在輪詢任務的狀態。併發
package com.ibm.scheduler; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; public class ScheduledExecutorTest implements Runnable { private String jobName = ""; public ScheduledExecutorTest(String jobName) { super(); this.jobName = jobName; } @Override public void run() { System.out.println("execute " + jobName); } public static void main(String[] args) { ScheduledExecutorService service = Executors.newScheduledThreadPool(10); long initialDelay1 = 1; long period1 = 1; // 從如今開始1秒鐘以後,每隔1秒鐘執行一次job1 service.scheduleAtFixedRate( new ScheduledExecutorTest("job1"), initialDelay1, period1, TimeUnit.SECONDS); long initialDelay2 = 1; long delay2 = 1; // 從如今開始2秒鐘以後,每隔2秒鐘執行一次job2 service.scheduleWithFixedDelay( new ScheduledExecutorTest("job2"), initialDelay2, delay2, TimeUnit.SECONDS); } } Output: execute job1 execute job1 execute job2 execute job1 execute job1 execute job2
清單 2 展現了 ScheduledExecutorService 中兩種最經常使用的調度方法 ScheduleAtFixedRate 和 ScheduleWithFixedDelay。ScheduleAtFixedRate 每次執行時間爲上一次任務開始起向後推一個時間間隔,即每次執行時間爲 :initialDelay, initialDelay+period, initialDelay+2*period, …;ScheduleWithFixedDelay 每次執行時間爲上一次任務結束起向後推一個時間間隔,即每次執行時間爲:initialDelay, initialDelay+executeTime+delay, initialDelay+2*executeTime+2*delay。因而可知,ScheduleAtFixedRate 是基於固定時間間隔進行任務調度,ScheduleWithFixedDelay 取決於每次任務執行的時間長短,是基於不固定時間間隔進行任務調度。app
回頁首框架
Timer 和 ScheduledExecutor 都僅能提供基於開始時間與重複間隔的任務調度,不能勝任更加複雜的調度需求。好比,設置每星期二的 16:38:10 執行任務。該功能使用 Timer 和 ScheduledExecutor 都不能直接實現,但咱們能夠藉助 Calendar 間接實現該功能。ide
package com.ibm.scheduler; import java.util.Calendar; import java.util.Date; import java.util.TimerTask; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; public class ScheduledExceutorTest2 extends TimerTask { private String jobName = ""; public ScheduledExceutorTest2(String jobName) { super(); this.jobName = jobName; } @Override public void run() { System.out.println("Date = "+new Date()+", execute " + jobName); } /** * 計算從當前時間currentDate開始,知足條件dayOfWeek, hourOfDay, * minuteOfHour, secondOfMinite的最近時間 * @return */ public Calendar getEarliestDate(Calendar currentDate, int dayOfWeek, int hourOfDay, int minuteOfHour, int secondOfMinite) { //計算當前時間的WEEK_OF_YEAR,DAY_OF_WEEK, HOUR_OF_DAY, MINUTE,SECOND等各個字段值 int currentWeekOfYear = currentDate.get(Calendar.WEEK_OF_YEAR); int currentDayOfWeek = currentDate.get(Calendar.DAY_OF_WEEK); int currentHour = currentDate.get(Calendar.HOUR_OF_DAY); int currentMinute = currentDate.get(Calendar.MINUTE); int currentSecond = currentDate.get(Calendar.SECOND); //若是輸入條件中的dayOfWeek小於當前日期的dayOfWeek,則WEEK_OF_YEAR須要推遲一週 boolean weekLater = false; if (dayOfWeek < currentDayOfWeek) { weekLater = true; } else if (dayOfWeek == currentDayOfWeek) { //當輸入條件與當前日期的dayOfWeek相等時,若是輸入條件中的 //hourOfDay小於當前日期的 //currentHour,則WEEK_OF_YEAR須要推遲一週 if (hourOfDay < currentHour) { weekLater = true; } else if (hourOfDay == currentHour) { //當輸入條件與當前日期的dayOfWeek, hourOfDay相等時, //若是輸入條件中的minuteOfHour小於當前日期的 //currentMinute,則WEEK_OF_YEAR須要推遲一週 if (minuteOfHour < currentMinute) { weekLater = true; } else if (minuteOfHour == currentSecond) { //當輸入條件與當前日期的dayOfWeek, hourOfDay, //minuteOfHour相等時,若是輸入條件中的 //secondOfMinite小於當前日期的currentSecond, //則WEEK_OF_YEAR須要推遲一週 if (secondOfMinite < currentSecond) { weekLater = true; } } } } if (weekLater) { //設置當前日期中的WEEK_OF_YEAR爲當前周推遲一週 currentDate.set(Calendar.WEEK_OF_YEAR, currentWeekOfYear + 1); } // 設置當前日期中的DAY_OF_WEEK,HOUR_OF_DAY,MINUTE,SECOND爲輸入條件中的值。 currentDate.set(Calendar.DAY_OF_WEEK, dayOfWeek); currentDate.set(Calendar.HOUR_OF_DAY, hourOfDay); currentDate.set(Calendar.MINUTE, minuteOfHour); currentDate.set(Calendar.SECOND, secondOfMinite); return currentDate; } public static void main(String[] args) throws Exception { ScheduledExceutorTest2 test = new ScheduledExceutorTest2("job1"); //獲取當前時間 Calendar currentDate = Calendar.getInstance(); long currentDateLong = currentDate.getTime().getTime(); System.out.println("Current Date = " + currentDate.getTime().toString()); //計算知足條件的最近一次執行時間 Calendar earliestDate = test .getEarliestDate(currentDate, 3, 16, 38, 10); long earliestDateLong = earliestDate.getTime().getTime(); System.out.println("Earliest Date = " + earliestDate.getTime().toString()); //計算從當前時間到最近一次執行時間的時間間隔 long delay = earliestDateLong - currentDateLong; //計算執行週期爲一星期 long period = 7 * 24 * 60 * 60 * 1000; ScheduledExecutorService service = Executors.newScheduledThreadPool(10); //從如今開始delay毫秒以後,每隔一星期執行一次job1 service.scheduleAtFixedRate(test, delay, period, TimeUnit.MILLISECONDS); } } Output: Current Date = Wed Feb 02 17:32:01 CST 2011 Earliest Date = Tue Feb 8 16:38:10 CST 2011 Date = Tue Feb 8 16:38:10 CST 2011, execute job1 Date = Tue Feb 15 16:38:10 CST 2011, execute job1
清單 3 實現了每星期二 16:38:10 調度任務的功能。其核心在於根據當前時間推算出最近一個星期二 16:38:10 的絕對時間,而後計算與當前時間的時間差,做爲調用 ScheduledExceutor 函數的參數。計算最近時間要用到 java.util.calendar 的功能。首先須要解釋 calendar 的一些設計思想。Calendar 有如下幾種惟一標識一個日期的組合方式:
YEAR + MONTH + DAY_OF_MONTH YEAR + MONTH + WEEK_OF_MONTH + DAY_OF_WEEK YEAR + MONTH + DAY_OF_WEEK_IN_MONTH + DAY_OF_WEEK YEAR + DAY_OF_YEAR YEAR + DAY_OF_WEEK + WEEK_OF_YEAR
上述組合分別加上 HOUR_OF_DAY + MINUTE + SECOND 即爲一個完整的時間標識。本例採用了最後一種組合方式。輸入爲 DAY_OF_WEEK, HOUR_OF_DAY, MINUTE, SECOND 以及當前日期 , 輸出爲一個知足 DAY_OF_WEEK, HOUR_OF_DAY, MINUTE, SECOND 而且距離當前日期最近的將來日期。計算的原則是從輸入的 DAY_OF_WEEK 開始比較,若是小於當前日期的 DAY_OF_WEEK,則須要向 WEEK_OF_YEAR 進一, 即將當前日期中的 WEEK_OF_YEAR 加一併覆蓋舊值;若是等於當前的 DAY_OF_WEEK, 則繼續比較 HOUR_OF_DAY;若是大於當前的 DAY_OF_WEEK,則直接調用 java.util.calenda 的 calendar.set(field, value) 函數將當前日期的 DAY_OF_WEEK, HOUR_OF_DAY, MINUTE, SECOND 賦值爲輸入值,依次類推,直到比較至 SECOND。讀者能夠根據輸入需求選擇不一樣的組合方式來計算最近執行時間。
能夠看出,用上述方法實現該任務調度比較麻煩,這就須要一個更加完善的任務調度框架來解決這些複雜的調度問題。幸運的是,開源工具包 Quartz 與 JCronTab 提供了這方面強大的支持。
Quartz 能夠知足更多更復雜的調度需求,首先讓咱們看看如何用 Quartz 實現每星期二 16:38 的調度安排:
package com.ibm.scheduler; import java.util.Date; import org.quartz.Job; import org.quartz.JobDetail; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.quartz.Scheduler; import org.quartz.SchedulerFactory; import org.quartz.Trigger; import org.quartz.helpers.TriggerUtils; public class QuartzTest implements Job { @Override //該方法實現須要執行的任務 public void execute(JobExecutionContext arg0) throws JobExecutionException { System.out.println("Generating report - " + arg0.getJobDetail().getFullName() + ", type =" + arg0.getJobDetail().getJobDataMap().get("type")); System.out.println(new Date().toString()); } public static void main(String[] args) { try { // 建立一個Scheduler SchedulerFactory schedFact = new org.quartz.impl.StdSchedulerFactory(); Scheduler sched = schedFact.getScheduler(); sched.start(); // 建立一個JobDetail,指明name,groupname,以及具體的Job類名, //該Job負責定義須要執行任務 JobDetail jobDetail = new JobDetail("myJob", "myJobGroup", QuartzTest.class); jobDetail.getJobDataMap().put("type", "FULL"); // 建立一個每週觸發的Trigger,指明星期幾幾點幾分執行 Trigger trigger = TriggerUtils.makeWeeklyTrigger(3, 16, 38); trigger.setGroup("myTriggerGroup"); // 從當前時間的下一秒開始執行 trigger.setStartTime(TriggerUtils.getEvenSecondDate(new Date())); // 指明trigger的name trigger.setName("myTrigger"); // 用scheduler將JobDetail與Trigger關聯在一塊兒,開始調度任務 sched.scheduleJob(jobDetail, trigger); } catch (Exception e) { e.printStackTrace(); } } } Output: Generating report - myJobGroup.myJob, type =FULL Tue Feb 8 16:38:00 CST 2011 Generating report - myJobGroup.myJob, type =FULL Tue Feb 15 16:38:00 CST 2011
清單 4 很是簡潔地實現了一個上述複雜的任務調度。Quartz 設計的核心類包括 Scheduler, Job 以及 Trigger。其中,Job 負責定義須要執行的任務,Trigger 負責設置調度策略,Scheduler 將兩者組裝在一塊兒,並觸發任務開始執行。
使用者只須要建立一個 Job 的繼承類,實現 execute 方法。JobDetail 負責封裝 Job 以及 Job 的屬性,並將其提供給 Scheduler 做爲參數。每次 Scheduler 執行任務時,首先會建立一個 Job 的實例,而後再調用 execute 方法執行。Quartz 沒有爲 Job 設計帶參數的構造函數,所以須要經過額外的 JobDataMap 來存儲 Job 的屬性。JobDataMap 能夠存儲任意數量的 Key,Value 對,例如:
jobDetail.getJobDataMap().put("myDescription", "my job description"); jobDetail.getJobDataMap().put("myValue", 1998); ArrayList<String> list = new ArrayList<String>(); list.add("item1"); jobDetail.getJobDataMap().put("myArray", list);
JobDataMap 中的數據能夠經過下面的方式獲取:
public class JobDataMapTest implements Job { @Override public void execute(JobExecutionContext context) throws JobExecutionException { //從context中獲取instName,groupName以及dataMap String instName = context.getJobDetail().getName(); String groupName = context.getJobDetail().getGroup(); JobDataMap dataMap = context.getJobDetail().getJobDataMap(); //從dataMap中獲取myDescription,myValue以及myArray String myDescription = dataMap.getString("myDescription"); int myValue = dataMap.getInt("myValue"); ArrayList<String> myArray = (ArrayListlt;Strin>) dataMap.get("myArray"); System.out.println(" Instance =" + instName + ", group = " + groupName + ", description = " + myDescription + ", value =" + myValue + ", array item0 = " + myArray.get(0)); } } Output: Instance = myJob, group = myJobGroup, description = my job description, value =1998, array item0 = item1
Trigger 的做用是設置調度策略。Quartz 設計了多種類型的 Trigger,其中最經常使用的是 SimpleTrigger 和 CronTrigger。
SimpleTrigger 適用於在某一特定的時間執行一次,或者在某一特定的時間以某一特定時間間隔執行屢次。上述功能決定了 SimpleTrigger 的參數包括 start-time, end-time, repeat count, 以及 repeat interval。
Repeat count 取值爲大於或等於零的整數,或者常量 SimpleTrigger.REPEAT_INDEFINITELY。
Repeat interval 取值爲大於或等於零的長整型。當 Repeat interval 取值爲零而且 Repeat count 取值大於零時,將會觸發任務的併發執行。
Start-time 與 dnd-time 取值爲 java.util.Date。當同時指定 end-time 與 repeat count 時,優先考慮 end-time。通常地,能夠指定 end-time,並設定 repeat count 爲 REPEAT_INDEFINITELY。
如下是 SimpleTrigger 的構造方法:
public SimpleTrigger(String name, String group, Date startTime, Date endTime, int repeatCount, long repeatInterval)
舉例以下:
建立一個當即執行且僅執行一次的 SimpleTrigger:
SimpleTrigger trigger= new SimpleTrigger("myTrigger", "myGroup", new Date(), null, 0, 0L);
建立一個半分鐘後開始執行,且每隔一分鐘重複執行一次的 SimpleTrigger:
SimpleTrigger trigger= new SimpleTrigger("myTrigger", "myGroup", new Date(System.currentTimeMillis()+30*1000), null, 0, 60*1000);
建立一個 2011 年 6 月 1 日 8:30 開始執行,每隔一小時執行一次,一共執行一百次,一天以後截止的 SimpleTrigger:
Calendar calendar = Calendar.getInstance(); calendar.set(Calendar.YEAR, 2011); calendar.set(Calendar.MONTH, Calendar.JUNE); calendar.set(Calendar.DAY_OF_MONTH, 1); calendar.set(Calendar.HOUR, 8); calendar.set(Calendar.MINUTE, 30); calendar.set(Calendar.SECOND, 0); calendar.set(Calendar.MILLISECOND, 0); Date startTime = calendar.getTime(); Date endTime = new Date (calendar.getTimeInMillis() +24*60*60*1000); SimpleTrigger trigger=new SimpleTrigger("myTrigger", "myGroup", startTime, endTime, 100, 60*60*1000);
上述最後一個例子中,同時設置了 end-time 與 repeat count,則優先考慮 end-time,總共能夠執行二十四次。
CronTrigger 的用途更廣,相比基於特定時間間隔進行調度安排的 SimpleTrigger,CronTrigger 主要適用於基於日曆的調度安排。例如:每星期二的 16:38:10 執行,每個月一號執行,以及更復雜的調度安排等。
CronTrigger 一樣須要指定 start-time 和 end-time,其核心在於 Cron 表達式,由七個字段組成:
Seconds Minutes Hours Day-of-Month Month Day-of-Week Year (Optional field)
舉例以下:
建立一個每三小時執行的 CronTrigger,且從每小時的整點開始執行:
0 0 0/3 * * ?
建立一個每十分鐘執行的 CronTrigger,且從每小時的第三分鐘開始執行:
0 3/10 * * * ?
建立一個每週一,週二,週三,週六的晚上 20:00 到 23:00,每半小時執行一次的 CronTrigger:
0 0/30 20-23 ? * MON-WED,SAT
建立一個每個月最後一個週四,中午 11:30-14:30,每小時執行一次的 trigger:
0 30 11-14/1 ? * 5L
解釋一下上述例子中各符號的含義:
首先全部字段都有本身特定的取值,例如,Seconds 和 Minutes 取值爲 0 到 59,Hours 取值爲 0 到 23,Day-of-Month 取值爲 0-31, Month 取值爲 0-11,或者 JAN,FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC,Days-of-Week 取值爲 1-7 或者 SUN, MON, TUE, WED, THU, FRI, SAT。每一個字段能夠取單個值,多個值,或一個範圍,例如 Day-of-Week 可取值爲「MON,TUE,SAT」,「MON-FRI」或者「TUE-THU,SUN」。
通配符 * 表示該字段可接受任何可能取值。例如 Month 字段賦值 * 表示每月,Day-of-Week 字段賦值 * 表示一週的天天。
/ 表示開始時刻與間隔時段。例如 Minutes 字段賦值 2/10 表示在一個小時內每 20 分鐘執行一次,從第 2 分鐘開始。
? 僅適用於 Day-of-Month 和 Day-of-Week。? 表示對該字段不指定特定值。適用於須要對這兩個字段中的其中一個指定值,而對另外一個不指定值的狀況。通常狀況下,這兩個字段只需對一個賦值。
L 僅適用於 Day-of-Month 和 Day-of-Week。L 用於 Day-of-Month 表示該月最後一天。L 單獨用於 Day-of-Week 表示週六,不然表示一個月最後一個星期幾,例如 5L 或者 THUL 表示該月最後一個星期四。
W 僅適用於 Day-of-Month,表示離指定日期最近的一個工做日,例如 Day-of-Month 賦值爲 10W 表示該月離 10 號最近的一個工做日。
# 僅適用於 Day-of-Week,表示該月第 XXX 個星期幾。例如 Day-of-Week 賦值爲 5#2 或者 THU#2,表示該月第二個星期四。
CronTrigger 的使用以下:
CronTrigger cronTrigger = new CronTrigger("myTrigger", "myGroup"); try { cronTrigger.setCronExpression("0 0/30 20-13 ? * MON-WED,SAT"); } catch (Exception e) { e.printStackTrace(); }
Job 與 Trigger 的鬆耦合設計是 Quartz 的一大特色,其優勢在於同一個 Job 能夠綁定多個不一樣的 Trigger,同一個 Trigger 也能夠調度多個 Job,靈活性很強。
除了上述基本的調度功能,Quartz 還提供了 listener 的功能。主要包含三種 listener:JobListener,TriggerListener 以及 SchedulerListener。當系統發生故障,相關人員須要被通知時,Listener 便能發揮它的做用。最多見的狀況是,當任務被執行時,系統發生故障,Listener 監聽到錯誤,當即發送郵件給管理員。下面給出 JobListener 的實例:
import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.quartz.JobListener; import org.quartz.SchedulerException; public class MyListener implements JobListener{ @Override public String getName() { return "My Listener"; } @Override public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) { if(jobException != null){ try { //中止Scheduler context.getScheduler().shutdown(); System.out.println(" Error occurs when executing jobs, shut down the scheduler "); // 給管理員發送郵件… } catch (SchedulerException e) { e.printStackTrace(); } } } }
從清單 7 能夠看出,使用者只須要建立一個 JobListener 的繼承類,重載須要觸發的方法便可。固然,須要將 listener 的實現類註冊到 Scheduler 和 JobDetail 中:
sched.addJobListener(new MyListener()); jobDetail.addJobListener("My Listener"); // listener 的名字
使用者也能夠將 listener 註冊爲全局 listener,這樣即可以監聽 scheduler 中註冊的全部任務 :
sched.addGlobalJobListener(new MyListener());
爲了測試 listener 的功能,能夠在 job 的 execute 方法中強制拋出異常。清單 7 中,listener 接收到異常,將 job 所在的 scheduler 停掉,阻止後續的 job 繼續執行。scheduler、jobDetail 等信息均可以從 listener 的參數 context 中檢索到。
清單 7 的輸出結果爲:
Generating report - myJob.myJob, type =FULL Tue Feb 15 18:57:35 CST 2011 2011-2-15 18:57:35 org.quartz.core.JobRunShell run 信息 : Job myJob.myJob threw a JobExecutionException: org.quartz.JobExecutionException at com.ibm.scheduler.QuartzListenerTest.execute(QuartzListenerTest.java:22) at org.quartz.core.JobRunShell.run(JobRunShell.java:191) at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:516) 2011-2-15 18:57:35 org.quartz.core.QuartzScheduler shutdown 信息 : Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED shutting down. Error occurs when executing jobs, shut down the scheduler
TriggerListener、SchedulerListener 與 JobListener 有相似的功能,只是各自觸發的事件不一樣,如 JobListener 觸發的事件爲:
Job to be executed, Job has completed execution 等
TriggerListener 觸發的事件爲:
Trigger firings, trigger mis-firings, trigger completions 等
SchedulerListener 觸發的事件爲:
add a job/trigger, remove a job/trigger, shutdown a scheduler 等
讀者能夠根據本身的需求重載相應的事件。
Quartz 的另外一顯著優勢在於持久化,即將任務調度的相關數據保存下來。這樣,當系統重啓後,任務被調度的狀態依然存在於系統中,不會丟失。默認狀況下,Quartz 採用的是 org.quartz.simpl.RAMJobStore,在這種狀況下,數據僅能保存在內存中,系統重啓後會所有丟失。若想持久化數據,須要採用 org.quartz.simpl.JDBCJobStoreTX。
實現持久化的第一步,是要建立 Quartz 持久化所須要的表格。在 Quartz 的發佈包 docs/dbTables 中能夠找到相應的表格建立腳本。Quartz 支持目前大部分流行的數據庫。本文以 DB2 爲例,所須要的腳本爲 tables_db2.sql。首先須要對腳本作一點小的修改,即在開頭指明 Schema:
SET CURRENT SCHEMA quartz;
爲了方便重複使用 , 建立表格前首先刪除以前的表格:drop table qrtz_job_details;
drop table qrtz_job_listeners;
…
而後建立數據庫 sched,執行 tables_db2.sql 建立持久化所須要的表格。
第二步,配置數據源。數據源與其它全部配置,例如 ThreadPool,均放在 quartz.properties 裏:
# Configure ThreadPool org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool org.quartz.threadPool.threadCount = 5 org.quartz.threadPool.threadPriority = 4 # Configure Datasources org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate org.quartz.jobStore.dataSource = db2DS org.quartz.jobStore.tablePrefix = QRTZ_ org.quartz.dataSource.db2DS.driver = com.ibm.db2.jcc.DB2Driver org.quartz.dataSource.db2DS.URL = jdbc:db2://localhost:50001/sched org.quartz.dataSource.db2DS.user = quartz org.quartz.dataSource.db2DS.password = passw0rd org.quartz.dataSource.db2DS.maxConnections = 5
使用時只須要將 quatz.properties 放在 classpath 下面,不用更改一行代碼,再次運行以前的任務調度實例,trigger、job 等信息便會被記錄在數據庫中。
將清單 4 中的 makeWeeklyTrigger 改爲 makeSecondlyTrigger,從新運行 main 函數,在 sched 數據庫中查詢表 qrtz_simple_triggers 中的數據。其查詢語句爲「db2 ‘ select repeat_interval, times_triggered from qrtz_simple_triggers ’」。結果 repeat_interval 爲 1000,與程序中設置的 makeSecondlyTrigger 相吻合,times_triggered 值爲 21。
停掉程序,將數據庫中記錄的任務調度數據從新導入程序運行:
package com.ibm.scheduler; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.quartz.SchedulerFactory; import org.quartz.Trigger; import org.quartz.impl.StdSchedulerFactory; public class QuartzReschedulerTest { public static void main(String[] args) throws SchedulerException { // 初始化一個 Schedule Factory SchedulerFactory schedulerFactory = new StdSchedulerFactory(); // 從 schedule factory 中獲取 scheduler Scheduler scheduler = schedulerFactory.getScheduler(); // 從 schedule factory 中獲取 trigger Trigger trigger = scheduler.getTrigger("myTrigger", "myTriggerGroup"); // 從新開啓調度任務 scheduler.rescheduleJob("myTrigger", "myTriggerGroup", trigger); scheduler.start(); } }
清單 9 中,schedulerFactory.getScheduler() 將 quartz.properties 的內容加載到內存,而後根據數據源的屬性初始化數據庫的連接,並將數據庫中存儲的數據加載到內存。以後,即可以在內存中查詢某一具體的 trigger,並將其從新啓動。這時候從新查詢 qrtz_simple_triggers 中的數據,發現 times_triggered 值比原來增加了。
習慣使用 unix/linux 的開發人員應該對 crontab 都不陌生。Crontab 是一個很是方便的用於 unix/linux 系統的任務調度命令。JCronTab 則是一款徹底按照 crontab 語法編寫的 java 任務調度工具。
首先簡單介紹一下 crontab 的語法,與上面介紹的 Quartz 很是類似,但更加簡潔 , 集中了最經常使用的語法。主要由六個字段組成(括弧中標識了每一個字段的取值範圍):
Minutes (0-59) Hours (0-23) Day-of-Month (1-31) Month (1-12/JAN-DEC) Day-of-Week (0-6/SUN-SAT) Command
與 Quartz 相比,省略了 Seconds 與 Year,多了一個 command 字段,即爲將要被調度的命令。JCronTab 中也包含符號「*」與「/」, 其含義與 Quartz 相同。
舉例以下:
天天 12 點到 15 點 , 每隔 1 小時執行一次 Date 命令:
0 12-15/1 * * * Date
每個月 2 號凌晨 1 點發一封信給 zhjingbj@cn.ibm.com:
0 1 2 * * mail -s 「good」 zhjingbj@cn.ibm.com
每週一,週二,週三,週六的晚上 20:00 到 23:00,每半小時打印「normal」:
0/30 20-23 * * MON-WED,SAT echo 「normal」
JCronTab 借鑑了 crontab 的語法,其區別在於 command 再也不是 unix/linux 的命令,而是一個 Java 類。若是該類帶參數,例如「com.ibm.scheduler.JCronTask2#run」,則按期執行 run 方法;若是該類不帶參數,則默認執行 main 方法。此外,還能夠傳參數給 main 方法或者構造函數,例如「com.ibm.scheduler.JCronTask2#run Hello World「表示傳兩個參數 Hello 和 World 給構造函數。
JCronTab 與 Quartz 相比,其優勢在於,第一,支持多種任務調度的持久化方法,包括普通文件、數據庫以及 XML 文件進行持久化;第二,JCronTab 可以很是方便地與 Web 應用服務器相結合,任務調度能夠隨 Web 應用服務器的啓動自動啓動;第三,JCronTab 還內置了發郵件功能,能夠將任務執行結果方便地發送給須要被通知的人。
JCronTab 與 Web 應用服務器的結合很是簡單,只須要在 Web 應用程序的 web.xml 中添加以下行:
<servlet> <servlet-name>LoadOnStartupServlet</servlet-name> <servlet-class>org.jcrontab.web.loadCrontabServlet</servlet-class> <init-param> <param-name>PROPERTIES_FILE</param-name> <param-value>D:/Scheduler/src/jcrontab.properties</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <!-- Mapping of the StartUp Servlet --> <servlet-mapping> <servlet-name>LoadOnStartupServlet</servlet-name> <url-pattern>/Startup</url-pattern> </servlet-mapping>
在清單 10 中,須要注意兩點:第一,必須指定 servlet-class 爲 org.jcrontab.web.loadCrontabServlet,由於它是整個任務調度的入口;第二,必須指定一個參數爲 PROPERTIES_FILE,才能被 loadCrontabServlet 識別。
接下來,須要撰寫 D:/Scheduler/src/jcrontab.properties 的內容,其內容根據需求的不一樣而改變。
當採用普通文件持久化時,jcrontab.properties 的內容主要包括:
org.jcrontab.data.file = D:/Scheduler/src/crontab org.jcrontab.data.datasource = org.jcrontab.data.FileSource
其中數據來源 org.jcrontab.data.datasource 被描述爲普通文件,即 org.jcrontab.data.FileSource。具體的文件即 org.jcrontab.data.file 指明爲 D:/Scheduler/src/crontab。
Crontab 描述了任務的調度安排:
*/2 * * * * com.ibm.scheduler.JCronTask1 * * * * * com.ibm.scheduler.JCronTask2#run Hello World
其中包含了兩條任務的調度,分別是每兩分鐘執行一次 JCronTask1 的 main 方法,每一分鐘執行一次 JCronTask2 的 run 方法。
package com.ibm.scheduler; import java.util.Date; public class JCronTask1 { private static int count = 0; public static void main(String[] args) { System.out.println("--------------Task1-----------------"); System.out.println("Current Time = " + new Date() + ", Count = " + count++); } } package com.ibm.scheduler; import java.util.Date; public class JCronTask2 implements Runnable { private static int count = 0; private static String[] args; public JCronTask2(String[] args) { System.out.println("--------------Task2-----------------"); System.out.println("Current Time = " + new Date() + ", Count = " + count++); JCronTask2.args = args; } @Override public void run() { System.out.println("enter into run method"); if (args != null && args.length > 0) { for (int i = 0; i < args.length; i++) { System.out.print("This is arg " + i + " " + args[i] + "\n"); } } } }
到此爲止,基於普通文件持久化的 JCronTab 的實例就所有配置好了。啓動 Web 應用服務器,即可以看到任務調度的輸出結果:
--------------Task2----------------- Current Time = Tue Feb 15 09:22:00 CST 2011, Count = 0 enter into run method This is arg 0 Hello This is arg 1 World --------------Task1----------------- Current Time = Tue Feb 15 09:22:00 CST 2011, Count = 0 --------------Task2----------------- Current Time = Tue Feb 15 09:23:00 CST 2011, Count = 1 enter into run method This is arg 0 Hello This is arg 1 World --------------Task2----------------- Current Time = Tue Feb 15 09:24:00 CST 2011, Count = 2 enter into run method This is arg 0 Hello This is arg 1 World --------------Task1----------------- Current Time = Tue Feb 15 09:24:00 CST 2011, Count = 1
經過修改 jcrontab.properties 中 datasource,能夠選擇採用數據庫或 xml 文件持久化,感興趣的讀者能夠參考 進階學習 JCronTab。
此外,JCronTab 還內置了發郵件功能,能夠將任務執行結果方便地發送給須要被通知的人。其配置很是簡單,只須要在 jcontab.properties 中添加幾行配置便可:
org.jcrontab.sendMail.to= Ther email you want to send to org.jcrontab.sendMail.from=The email you want to send from org.jcrontab.sendMail.smtp.host=smtp server org.jcrontab.sendMail.smtp.user=smtp username org.jcrontab.sendMail.smtp.password=smtp password
本文介紹了四種經常使用的對任務進行調度的 Java 實現方法,即 Timer,ScheduledExecutor, Quartz 以及 JCronTab。文本對每種方法都進行了實例解釋,並對其優缺點進行比較。對於簡單的基於起始時間點與時間間隔的任務調度,使用 Timer 就足夠了;若是須要同時調度多個任務,基於線程池的 ScheduledTimer 是更爲合適的選擇;當任務調度的策略複雜到難以憑藉起始時間點與時間間隔來描述時,Quartz 與 JCronTab 則體現出它們的優點。熟悉 Unix/Linux 的開發人員更傾向於 JCronTab,且 JCronTab 更適合與 Web 應用服務器相結合。Quartz 的 Trigger 與 Job 鬆耦合設計使其更適用於 Job 與 Trigger 的多對多應用場景。