所有類型的 Java 應用程式一般都需要計劃重複執行的任務。公司專屬應用程式程式需要計劃每日的日誌或者晚間批處理過程。一個 J2SE 或者 J2ME 日曆應用程式需要根據使用者的約定計劃鬧鈴時間。不過,標準的調度類 Timer 和 TimerTask 沒有足夠的靈活性,無法支援通常需要的計劃任務類型。在本文中,Java 開發人員 Tom White 向您展示了如何構建一個簡單通用的計劃架構,以用於執行任意複雜的計劃任務。
我將把 java.util.Timer 和 java.util.TimerTask 統稱為 Java 計時器架構,它們使程式員可以很容易地計劃簡單的任務(注意這些類也可用於 J2ME 中)。在 Java 2 SDK, Standard Edition, Version 1.3 中引入這個架構之前,開發人員必須編寫自己的發送器,這需要花費很大精力來處理線程和複雜的 Object.wait() 方法。不過,Java 計時器架構沒有足夠的能力來滿足許多應用程式的計劃要求。甚至一項需要在每天同一時間重複執行的任務,也不能直接使用 Timer 來計劃,因為在夏令時開始和結束時會出現時間跳躍。
本文展示了一個通用的 Timer 和 TimerTask 計劃架構,從而允許更靈活的計劃任務。這個架構非常簡單 —— 它包括兩個類和一個介面 —— 並且容易掌握。如果您習慣於使用 Java 定時器架構,那麼您應該可以很快地掌握這個計劃架構。
計劃單次任務
計劃架構建立在 Java 定時器架構類的基礎之上。因此,在解釋如何使用計劃架構以及如何?它之前,我們將首先看看如何用這些類進行計劃。
想像一個煮蛋計時器,在數分鐘之後(這時蛋煮好了)它會發出聲音提醒您。清單 1 中的代碼構成了一個簡單的煮蛋計時器的基本結構,它用 Java 語言編寫:
清單 1. EggTimer 類
package org.tiling.scheduling.examples;import java.util.Timer;import java.util.TimerTask;public class EggTimer { private final Timer timer = new Timer(); private final int minutes; public EggTimer(int minutes) { this.minutes = minutes; } public void start() { timer.schedule(new TimerTask() { public void run() { playSound(); timer.cancel(); } private void playSound() { System.out.println("Your egg is ready!"); // Start a new thread to play a sound... } }, minutes * 60 * 1000); } public static void main(String[] args) { EggTimer eggTimer = new EggTimer(2); eggTimer.start(); }} |
EggTimer 執行個體擁有一個 Timer 執行個體,用於提供必要的計劃。用 start() 方法啟動煮蛋計時器後,它就計划了一個 TimerTask,在指定的分鐘數之後執行。時間到了,Timer 就在後台調用 TimerTask 的 start() 方法,這會使它發出聲音。在取消計時器後這個應用程式就會中止。
計劃重複執行的任務
通過指定一個固定的執行頻率或者固定的執行時間間隔,Timer 可以對重複執行的任務進行計劃。不過,有許多應用程式要求更複雜的計劃。例如,每天清晨在同一時間發出叫醒鈴聲的鬧鐘不能簡單地使用固定的計劃頻率 86400000 毫秒(24 小時),因為在鐘撥快或者撥慢(如果您的時區使用夏令時)的那些天裡,叫醒可能過晚或者過早。解決方案是使用日曆演算法計算每日事件下一次計劃發生的時間。而這正是計劃架構所支援的。考慮清單 2 中的 AlarmClock 實現:
清單 2. AlarmClock 類
package org.tiling.scheduling.examples;import java.text.SimpleDateFormat;import java.util.Date;import org.tiling.scheduling.Scheduler;import org.tiling.scheduling.SchedulerTask;import org.tiling.scheduling.examples.iterators.DailyIterator;public class AlarmClock { private final Scheduler scheduler = new Scheduler(); private final SimpleDateFormat dateFormat = new SimpleDateFormat("dd MMM yyyy HH:mm:ss.SSS"); private final int hourOfDay, minute, second; public AlarmClock(int hourOfDay, int minute, int second) { this.hourOfDay = hourOfDay; this.minute = minute; this.second = second; } public void start() { scheduler.schedule(new SchedulerTask() { public void run() { soundAlarm(); } private void soundAlarm() { System.out.println("Wake up! " + "It's " + dateFormat.format(new Date())); // Start a new thread to sound an alarm... } }, new DailyIterator(hourOfDay, minute, second)); } public static void main(String[] args) { AlarmClock alarmClock = new AlarmClock(7, 0, 0); alarmClock.start(); }} |
注意這段代碼與煮蛋計時器應用程式非常相似。AlarmClock 執行個體擁有一個 Scheduler (而不是 Timer)執行個體,用於提供必要的計劃。啟動後,這個鬧鐘對 SchedulerTask (而不是 TimerTask)進行調度用以發出警示聲。這個鬧鐘不是計劃一個任務在固定的延遲時間後執行,而是用 DailyIterator 類描述其計劃。在這裡,它只是計劃任務在每天上午 7:00 執行。下面是一個正常運行情況下的輸出:
Wake up! It's 24 Aug 2003 07:00:00.023Wake up! It's 25 Aug 2003 07:00:00.001Wake up! It's 26 Aug 2003 07:00:00.058Wake up! It's 27 Aug 2003 07:00:00.015Wake up! It's 28 Aug 2003 07:00:00.002... |
DailyIterator 實現了 ScheduleIterator,這是一個將 SchedulerTask 的計劃執行時間指定為一系列 java.util.Date 對象的介面。然後 next() 方法按時間先後順序迭代 Date 對象。傳回值 null 會使任務取消(即它再也不會運行)—— 這樣的話,試圖再次計劃將會拋出一個異常。清單 3 包含 ScheduleIterator 介面:
清單 3. ScheduleIterator 介面
package org.tiling.scheduling;import java.util.Date;public interface ScheduleIterator { public Date next();} |
DailyIterator 的 next() 方法返回表示每天同一時間(上午 7:00)的 Date 對象,如清單 4 所示。所以,如果對新構建的 next() 類調用 next(),那麼將會得到傳遞給建構函式的那個日期當天或者後面一天的 7:00 AM。再次調用 next() 會返回後一天的 7:00 AM,如此重複。為了實現這種行為,DailyIterator 使用了 java.util.Calendar 執行個體。建構函式會在日曆中加上一天,對日曆的這種設定使得第一次調用 next() 會返回正確的 Date。注意代碼沒有明確地提到夏令時修正,因為 Calendar 實現(在本例中是 GregorianCalendar)負責對此進行處理,所以不需要這樣做。
清單 4. DailyIterator 類
package org.tiling.scheduling.examples.iterators;import org.tiling.scheduling.ScheduleIterator;import java.util.Calendar;import java.util.Date;/** * A DailyIterator class returns a sequence of dates on subsequent days * representing the same time each day. */public class DailyIterator implements ScheduleIterator { private final int hourOfDay, minute, second; private final Calendar calendar = Calendar.getInstance(); public DailyIterator(int hourOfDay, int minute, int second) { this(hourOfDay, minute, second, new Date()); } public DailyIterator(int hourOfDay, int minute, int second, Date date) { this.hourOfDay = hourOfDay; this.minute = minute; this.second = second; calendar.setTime(date); calendar.set(Calendar.HOUR_OF_DAY, hourOfDay); calendar.set(Calendar.MINUTE, minute); calendar.set(Calendar.SECOND, second); calendar.set(Calendar.MILLISECOND, 0); if (!calendar.getTime().before(date)) { calendar.add(Calendar.DATE, -1); } } public Date next() { calendar.add(Calendar.DATE, 1); return calendar.getTime(); }} |