綜觀目前的 Web 應用,多數應用都具有任務調度的功能。本文由淺入深介紹了幾種任務調度的 Java 實現方法,包括 Timer,Scheduler, Quartz 以及 JCron Tab,並對其優缺點進行比較,目的在於給須要開發任務調度的程序員提供有價值的參考。java
任務調度是指基於給定時間點,給定時間間隔或者給定執行次數自動執行任務。本文由淺入深介紹四種任務調度的 Java 實現: 程序員
Timer併發
ScheduledExecutor框架
開源工具包 Quartzide
開源工具包 JCronTab函數
此外,爲結合實現複雜的任務調度,本文還將介紹 Calendar 的一些使用方法。工具
Timerthis
相信你們都已經很是熟悉 java.util.Timer 了,它是最簡單的一種實現任務調度的方法,下面給出一個具體的例子:spa
package org.js.test; import java.util.Date; import java.util.Timer; import java.util.TimerTask; /** * Created with IntelliJ IDEA. * User: dev02 * Date: 16-1-27 * Time: 下午2:55 * To change this template use File | Settings | File Templates. */ public class TimerTest extends TimerTask{ private String jobname=null; public TimerTest(String jobname) { this.jobname = jobname; } @Override public void run() { System.out.println("job:"+jobname); System.out.println("sysdate:"+(new Date())); try { Thread.currentThread().sleep(4300); } catch (InterruptedException e) { e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. } System.out.println("job:"+jobname); System.out.println("sysdate:"+(new Date())); //To change body of implemented methods use File | Settings | File Templates. } public static void main(String[] args){ Timer timer=new Timer(); long delay1=1*1000; long period=1500; //1s後,每隔1.5s執行一次 timer.schedule(new TimerTest("job1"),delay1,period); //2s後,每隔3s執行一次 long delay2=2*1000; long period2=3000; timer.schedule(new TimerTest("job2"),delay2,period2); } }
輸出結果:線程
使用 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 都是在輪詢任務的狀態。
package org.js.test; import java.util.Date; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; /** * Created with IntelliJ IDEA. * User: dev02 * Date: 16-1-28 * Time: 下午1:54 * To change this template use File | Settings | File Templates. */ public class ScheduledExecutorTest implements Runnable{ private String jobname=""; public ScheduledExecutorTest(String jobname) { this.jobname = jobname; } public void run() { System.out.println("job:"+jobname); System.out.println("sysdate:"+(new Date())); try { Thread.currentThread().sleep(4300); } catch (InterruptedException e) { e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. } System.out.println("job:"+jobname); System.out.println("sysdate:"+(new Date())); //To change body of implemented methods use File | Settings | File Templates. } public static void main(String[] args){ ScheduledExecutorService services= Executors.newScheduledThreadPool(10); long delay1=1; long period=2; //1s後,每隔1s執行一次 services.scheduleAtFixedRate( new ScheduledExecutorTest("job1"),delay1,period, TimeUnit.SECONDS); long delay2=2; long period2=3; services.scheduleAtFixedRate( new ScheduledExecutorTest("job2"),delay2,period2, TimeUnit.SECONDS); } }
輸出結果:
使用ScheduledExecutor能夠看出,咱們job執行的時候線程池中一個線程去執行。這和以前使用Timer的串行執行不一致。
ScheduledExecutor 和 Calendar 實現複雜任務調度
Timer 和 ScheduledExecutor 都僅能提供基於開始時間與重複間隔的任務調度,不能勝任更加複雜的調度需求。好比,設置每星期二的 16:38:10 執行任務。該功能使用 Timer 和 ScheduledExecutor 都不能直接實現,但咱們能夠藉助 Calendar 間接實現該功能。
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); } }
/** 輸出結果: 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 */
實現了每星期二 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 提供了這方面強大的支持。