關於Java中的調度問題,是比較常見的問題,一直沒有系統的梳理,現在梳理一下
注意:Quartz的例子 需要在特定的版本上執行,不同的版本使用方法不同,但是總的來說方法大同小異。本例子的版本是1.8
Timer
相信大家都已經非常熟悉 java.util.Timer 了,它是最簡單的一種實現任務調度的方法,下面給出一個具體的例子:
清單 1. 使用 Timer 進行任務調度
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 去執行即可。
Timer 的設計核心是一個 TaskList 和一個 TaskThread。Timer 將接收到的任務丟到自己的 TaskList 中,TaskList 按照 Task 的最初執行時間進行排序。TimerThread 在創建 Timer 時會啟動成為一個守護線程。這個線程會輪詢所有任務,找到一個最近要執行的任務,然后休眠,當到達最近要執行任務的開始時間點,TimerThread 被喚醒並執行該任務。之后 TimerThread 更新最近一個要執行的任務,繼續休眠。
Timer 的優點在於簡單易用,但由於所有任務都是由同一個線程來調度,因此所有任務都是串行執行的,同一時間只能有一個任務在執行,前一個任務的延遲或異常都將會影響到之后的任務。
ScheduledExecutor
鑒於 Timer 的上述缺陷,Java 5 推出了基於線程池設計的 ScheduledExecutor。其設計思想是,每一個被調度的任務都會由線程池中一個線程去執行,因此任務是並發執行的,相互之間不會受到干擾。需要注意的是,只有當任務的執行時間到來時,ScheduedExecutor 才會真正啟動一個線程,其余時間 ScheduledExecutor 都是在輪詢任務的狀態。
清單 2. 使用 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 取決於每次任務執行的時間長短,是基於不固定時間間隔進行任務調度。
Quartz
Quartz 可以滿足更多更復雜的調度需求,
清單 4. 使用 Quartz 進行任務調度
package com.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.SchedulerException; import org.quartz.SchedulerFactory; import org.quartz.SimpleTrigger; import org.quartz.Trigger; import org.quartz.TriggerUtils; import org.quartz.impl.StdSchedulerFactory; import com.scheduler.sample_official.MyJob; public class QuartzTest implements Job{ //這個例子使用的是Quartz 1.8版本的 //demo下載:http://download.csdn.net/download/llhhyy1989/5536893 //講解:http://blog.csdn.net/yuebinghaoyuan/article/details/9045471 //https://www.ibm.com/developerworks/cn/java/j-lo-taskschedule/ public static void main(String[] args) throws SchedulerException { // TODO Auto-generated method stub //創建一個Scheduler SchedulerFactory schedFact=new 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"); // 定義調度觸發規則,比如每1秒運行一次,共運行8次 SimpleTrigger simpleTrigger=new SimpleTrigger("simpleTrigger","triggerGroup"); // 馬上啟動 simpleTrigger.setStartTime(new Date()); // 間隔時間 simpleTrigger.setRepeatInterval(1000); // 運行次數 simpleTrigger.setRepeatCount(8); //用scheduler將JobDetail與Trigger關聯在一起,開始調度任務; sched.scheduleJob(jobDetail,simpleTrigger); } // public static void main(String[] args) throws SchedulerException { // // TODO Auto-generated method stub // //創建一個Scheduler // SchedulerFactory schedFact=new 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"); // //// 定義調度觸發規則,比如每1秒運行一次,共運行8次 // SimpleTrigger simpleTrigger=new SimpleTrigger("simpleTrigger","triggerGroup"); //// 馬上啟動 // simpleTrigger.setStartTime(new Date()); //// 間隔時間 // simpleTrigger.setRepeatInterval(1000); //// 運行次數 // simpleTrigger.setRepeatCount(8); // // //用scheduler將JobDetail與Trigger關聯在一起,開始調度任務; // sched.scheduleJob(jobDetail,simpleTrigger); // // // } @Override public void execute(JobExecutionContext arg0) throws JobExecutionException { // TODO Auto-generated method stub System.out.println("Generating Report - " +arg0.getJobDetail().getFullName()+",type=" +arg0.getJobDetail().getJobDataMap().get("type")); System.out.println(new Date().toString()); } }
清單 4 非常簡潔地實現了一個上述復雜的任務調度。Quartz 設計的核心類包括 Scheduler, Job 以及 Trigger。其中,Job 負責定義需要執行的任務,Trigger 負責設置調度策略,Scheduler 將二者組裝在一起,並觸發任務開始執行。
Job
使用者只需要創建一個 Job 的繼承類,實現 execute 方法。JobDetail 負責封裝 Job 以及 Job 的屬性,並將其提供給 Scheduler 作為參數。每次 Scheduler 執行任務時,首先會創建一個 Job 的實例,然后再調用 execute 方法執行。Quartz 沒有為 Job 設計帶參數的構造函數,因此需要通過額外的 JobDataMap 來存儲 Job 的屬性。JobDataMap 可以存儲任意數量的 Key,Value 對,例如:
清單 5. 為 JobDataMap 賦值
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 中的數據可以通過下面的方式獲取:
清單 6. 獲取 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
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,靈活性很強。
參考:Java任務調度教程實例
相應的例子:
http://download.csdn.net/detail/llhhyy1989/5536893
http://download.csdn.net/detail/llhhyy1989/5536977