kapok

          垃圾桶,嘿嘿,我藏的這么深你們還能找到啊,真牛!

            BlogJava :: 首頁(yè) :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理 ::
            455 隨筆 :: 0 文章 :: 76 評(píng)論 :: 0 Trackbacks
          Java語(yǔ)言中Timer類(lèi)的簡(jiǎn)潔用法



          所有類(lèi)型的 Java 應(yīng)用程序一般都需要計(jì)劃重復(fù)執(zhí)行的任務(wù)。企業(yè)應(yīng)用程序需要計(jì)劃每日的日志或者晚間批處理過(guò)程。一個(gè) J2SE 或者 J2ME 日歷應(yīng)用程序需要根據(jù)用戶(hù)的約定計(jì)劃鬧鈴時(shí)間。不過(guò),標(biāo)準(zhǔn)的調(diào)度類(lèi) Timer 和 TimerTask 沒(méi)有足夠的靈活性,無(wú)法支持通常需要的計(jì)劃任務(wù)類(lèi)型。在本文中,Java 開(kāi)發(fā)人員 Tom White 向您展示了如何構(gòu)建一個(gè)簡(jiǎn)單通用的計(jì)劃框架,以用于執(zhí)行任意復(fù)雜的計(jì)劃任務(wù)。

          我將把 java.util.Timer 和 java.util.TimerTask 統(tǒng)稱(chēng)為 Java 計(jì)時(shí)器框架,它們使程序員可以很容易地計(jì)劃簡(jiǎn)單的任務(wù)(注意這些類(lèi)也可用于 J2ME 中)。在 Java 2 SDK, Standard Edition, Version 1.3 中引入這個(gè)框架之前,開(kāi)發(fā)人員必須編寫(xiě)自己的調(diào)度程序,這需要花費(fèi)很大精力來(lái)處理線(xiàn)程和復(fù)雜的 Object.wait() 方法。不過(guò),Java 計(jì)時(shí)器框架沒(méi)有足夠的能力來(lái)滿(mǎn)足許多應(yīng)用程序的計(jì)劃要求。甚至一項(xiàng)需要在每天同一時(shí)間重復(fù)執(zhí)行的任務(wù),也不能直接使用 Timer 來(lái)計(jì)劃,因?yàn)樵谙牧顣r(shí)開(kāi)始和結(jié)束時(shí)會(huì)出現(xiàn)時(shí)間跳躍。

          本文展示了一個(gè)通用的 Timer 和 TimerTask 計(jì)劃框架,從而允許更靈活的計(jì)劃任務(wù)。這個(gè)框架非常簡(jiǎn)單 —— 它包括兩個(gè)類(lèi)和一個(gè)接口 —— 并且容易掌握。如果您習(xí)慣于使用 Java 定時(shí)器框架,那么您應(yīng)該可以很快地掌握這個(gè)計(jì)劃框架。

          計(jì)劃單次任務(wù)

          計(jì)劃框架建立在 Java 定時(shí)器框架類(lèi)的基礎(chǔ)之上。因此,在解釋如何使用計(jì)劃框架以及如何實(shí)現(xiàn)它之前,我們將首先看看如何用這些類(lèi)進(jìn)行計(jì)劃。

          想像一個(gè)煮蛋計(jì)時(shí)器,在數(shù)分鐘之后(這時(shí)蛋煮好了)它會(huì)發(fā)出聲音提醒您。清單 1 中的代碼構(gòu)成了一個(gè)簡(jiǎn)單的煮蛋計(jì)時(shí)器的基本結(jié)構(gòu),它用 Java 語(yǔ)言編寫(xiě):

          清單 1. EggTimer 類(lèi)

          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 實(shí)例擁有一個(gè) Timer 實(shí)例,用于提供必要的計(jì)劃。用 start() 方法啟動(dòng)煮蛋計(jì)時(shí)器后,它就計(jì)劃了一個(gè) TimerTask,在指定的分鐘數(shù)之后執(zhí)行。時(shí)間到了,Timer 就在后臺(tái)調(diào)用 TimerTask 的 start() 方法,這會(huì)使它發(fā)出聲音。在取消計(jì)時(shí)器后這個(gè)應(yīng)用程序就會(huì)中止。

          計(jì)劃重復(fù)執(zhí)行的任務(wù)

          通過(guò)指定一個(gè)固定的執(zhí)行頻率或者固定的執(zhí)行時(shí)間間隔,Timer 可以對(duì)重復(fù)執(zhí)行的任務(wù)進(jìn)行計(jì)劃。不過(guò),有許多應(yīng)用程序要求更復(fù)雜的計(jì)劃。例如,每天清晨在同一時(shí)間發(fā)出叫醒鈴聲的鬧鐘不能簡(jiǎn)單地使用固定的計(jì)劃頻率 86400000 毫秒(24 小時(shí)),因?yàn)樵阽姄芸旎蛘邠苈ㄈ绻臅r(shí)區(qū)使用夏令時(shí))的那些天里,叫醒可能過(guò)晚或者過(guò)早。解決方案是使用日歷算法計(jì)算每日事件下一次計(jì)劃發(fā)生的時(shí)間。 而這正是計(jì)劃框架所支持的。考慮清單 2 中的 AlarmClock 實(shí)現(xiàn):

          清單 2. AlarmClock 類(lèi)

          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&quots " + 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();
          }
          }


          注意這段代碼與煮蛋計(jì)時(shí)器應(yīng)用程序非常相似。AlarmClock 實(shí)例擁有一個(gè) Scheduler (而不是 Timer)實(shí)例,用于提供必要的計(jì)劃。啟動(dòng)后,這個(gè)鬧鐘對(duì) SchedulerTask (而不是 TimerTask)進(jìn)行調(diào)度用以發(fā)出報(bào)警聲。這個(gè)鬧鐘不是計(jì)劃一個(gè)任務(wù)在固定的延遲時(shí)間后執(zhí)行,而是用 DailyIterator 類(lèi)描述其計(jì)劃。在這里,它只是計(jì)劃任務(wù)在每天上午 7:00 執(zhí)行。下面是一個(gè)正常運(yùn)行情況下的輸出:

          Wake up! It&quots 24 Aug 2003 07:00:00.023
          Wake up! It&quots 25 Aug 2003 07:00:00.001
          Wake up! It&quots 26 Aug 2003 07:00:00.058
          Wake up! It&quots 27 Aug 2003 07:00:00.015
          Wake up! It&quots 28 Aug 2003 07:00:00.002
          ...


          DailyIterator 實(shí)現(xiàn)了 ScheduleIterator,這是一個(gè)將 SchedulerTask 的計(jì)劃執(zhí)行時(shí)間指定為一系列 java.util.Date 對(duì)象的接口。然后 next() 方法按時(shí)間先后順序迭代 Date 對(duì)象。返回值 null 會(huì)使任務(wù)取消(即它再也不會(huì)運(yùn)行)—— 這樣的話(huà),試圖再次計(jì)劃將會(huì)拋出一個(gè)異常。清單 3 包含 ScheduleIterator 接口:

          清單 3. ScheduleIterator 接口

          package org.tiling.scheduling;

          import java.util.Date;

          public interface ScheduleIterator {
          public Date next();
          }


          DailyIterator 的 next() 方法返回表示每天同一時(shí)間(上午 7:00)的 Date 對(duì)象,如清單 4 所示。所以,如果對(duì)新構(gòu)建的 next() 類(lèi)調(diào)用 next(),那么將會(huì)得到傳遞給構(gòu)造函數(shù)的那個(gè)日期當(dāng)天或者后面一天的 7:00 AM。再次調(diào)用 next() 會(huì)返回后一天的 7:00 AM,如此重復(fù)。為了實(shí)現(xiàn)這種行為,DailyIterator 使用了 java.util.Calendar 實(shí)例。構(gòu)造函數(shù)會(huì)在日歷中加上一天,對(duì)日歷的這種設(shè)置使得第一次調(diào)用 next() 會(huì)返回正確的 Date。注意代碼沒(méi)有明確地提到夏令時(shí)修正,因?yàn)?Calendar 實(shí)現(xiàn)(在本例中是 GregorianCalendar)負(fù)責(zé)對(duì)此進(jìn)行處理,所以不需要這樣做。

          清單 4. DailyIterator 類(lèi)

          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();
          }

          }


          實(shí)現(xiàn)計(jì)劃框架

          在上一節(jié),我們學(xué)習(xí)了如何使用計(jì)劃框架,并將它與 Java 定時(shí)器框架進(jìn)行了比較。下面,我將向您展示如何實(shí)現(xiàn)這個(gè)框架。除了 清單 3 中展示的 ScheduleIterator 接口,構(gòu)成這個(gè)框架的還有另外兩個(gè)類(lèi) —— Scheduler 和 SchedulerTask 。這些類(lèi)實(shí)際上在內(nèi)部使用 Timer 和 SchedulerTask,因?yàn)橛?jì)劃其實(shí)就是一系列的單次定時(shí)器。清單 5 和 6 顯示了這兩個(gè)類(lèi)的源代碼:

          清單 5. Scheduler

          package org.tiling.scheduling;

          import java.util.Date;
          import java.util.Timer;
          import java.util.TimerTask;

          public class Scheduler {

          class SchedulerTimerTask extends TimerTask {
          private SchedulerTask schedulerTask;
          private ScheduleIterator iterator;
          public SchedulerTimerTask(SchedulerTask schedulerTask,
          ScheduleIterator iterator) {
          this.schedulerTask = schedulerTask;
          this.iterator = iterator;
          }
          public void run() {
          schedulerTask.run();
          reschedule(schedulerTask, iterator);
          }
          }

          private final Timer timer = new Timer();

          public Scheduler() {
          }

          public void cancel() {
          timer.cancel();
          }

          public void schedule(SchedulerTask schedulerTask,
          ScheduleIterator iterator) {

          Date time = iterator.next();
          if (time == null) {
          schedulerTask.cancel();
          } else {
          synchronized(schedulerTask.lock) {
          if (schedulerTask.state != SchedulerTask.VIRGIN) {
          throw new IllegalStateException("Task already
          scheduled " + "or cancelled");
          }
          schedulerTask.state = SchedulerTask.SCHEDULED;
          schedulerTask.timerTask =
          new SchedulerTimerTask(schedulerTask, iterator);
          timer.schedule(schedulerTask.timerTask, time);
          }
          }
          }

          private void reschedule(SchedulerTask schedulerTask,
          ScheduleIterator iterator) {

          Date time = iterator.next();
          if (time == null) {
          schedulerTask.cancel();
          } else {
          synchronized(schedulerTask.lock) {
          if (schedulerTask.state != SchedulerTask.CANCELLED) {
          schedulerTask.timerTask =
          new SchedulerTimerTask(schedulerTask, iterator);
          timer.schedule(schedulerTask.timerTask, time);
          }
          }
          }
          }

          }


          清單 6 顯示了 SchedulerTask 類(lèi)的源代碼:

          package org.tiling.scheduling;

          import java.util.TimerTask;

          public abstract class SchedulerTask implements Runnable {

          final Object lock = new Object();

          int state = VIRGIN;
          static final int VIRGIN = 0;
          static final int SCHEDULED = 1;
          static final int CANCELLED = 2;

          TimerTask timerTask;

          protected SchedulerTask() {
          }

          public abstract void run();

          public boolean cancel() {
          synchronized(lock) {
          if (timerTask != null) {
          timerTask.cancel();
          }
          boolean result = (state == SCHEDULED);
          state = CANCELLED;
          return result;
          }
          }

          public long scheduledExecutionTime() {
          synchronized(lock) {
          return timerTask == null ? 0 : timerTask.scheduledExecutionTime();
          }
          }

          }


          就像煮蛋計(jì)時(shí)器,Scheduler 的每一個(gè)實(shí)例都擁有 Timer 的一個(gè)實(shí)例,用于提供底層計(jì)劃。Scheduler 并沒(méi)有像實(shí)現(xiàn)煮蛋計(jì)時(shí)器時(shí)那樣使用一個(gè)單次定時(shí)器,它將一組單次定時(shí)器串接在一起,以便在由 ScheduleIterator 指定的各個(gè)時(shí)間執(zhí)行 SchedulerTask 類(lèi)。

          考慮 Scheduler 上的 public schedule() 方法 —— 這是計(jì)劃的入口點(diǎn),因?yàn)樗强蛻?hù)調(diào)用的方法(在 取消任務(wù) 一節(jié)中將描述僅有的另一個(gè) public 方法 cancel())。通過(guò)調(diào)用 ScheduleIterator 接口的 next(),發(fā)現(xiàn)第一次執(zhí)行 SchedulerTask 的時(shí)間。然后通過(guò)調(diào)用底層 Timer 類(lèi)的單次 schedule() 方法,啟動(dòng)計(jì)劃在這一時(shí)刻執(zhí)行。為單次執(zhí)行提供的 TimerTask 對(duì)象是嵌入的 SchedulerTimerTask 類(lèi)的一個(gè)實(shí)例,它包裝了任務(wù)和迭代器(iterator)。在指定的時(shí)間,調(diào)用嵌入類(lèi)的 run() 方法,它使用包裝的任務(wù)和迭代器引用以便重新計(jì)劃任務(wù)的下一次執(zhí)行。reschedule() 方法與 schedule() 方法非常相似,只不過(guò)它是 private 的,并且執(zhí)行一組稍有不同的 SchedulerTask 狀態(tài)檢查。重新計(jì)劃過(guò)程反復(fù)重復(fù),為每次計(jì)劃執(zhí)行構(gòu)造一個(gè)新的嵌入類(lèi)實(shí)例,直到任務(wù)或者調(diào)度程序被取消(或者 JVM 關(guān)閉)。

          類(lèi)似于 TimerTask,SchedulerTask 在其生命周期中要經(jīng)歷一系列的狀態(tài)。創(chuàng)建后,它處于 VIRGIN 狀態(tài),這表明它從沒(méi)有計(jì)劃過(guò)。計(jì)劃以后,它就變?yōu)?SCHEDULED 狀態(tài),再用下面描述的方法之一取消任務(wù)后,它就變?yōu)?CANCELLED 狀態(tài)。管理正確的狀態(tài)轉(zhuǎn)變 —— 如保證不對(duì)一個(gè)非 VIRGIN 狀態(tài)的任務(wù)進(jìn)行兩次計(jì)劃 —— 增加了 Scheduler 和 SchedulerTask 類(lèi)的復(fù)雜性。在進(jìn)行可能改變?nèi)蝿?wù)狀態(tài)的操作時(shí),代碼必須同步任務(wù)的鎖對(duì)象。

          取消任務(wù)

          取消計(jì)劃任務(wù)有三種方式。第一種是調(diào)用 SchedulerTask 的 cancel() 方法。這很像調(diào)用 TimerTask 的 cancel()方法:任務(wù)再也不會(huì)運(yùn)行了,不過(guò)已經(jīng)運(yùn)行的任務(wù)仍會(huì)運(yùn)行完成。 cancel() 方法的返回值是一個(gè)布爾值,表示如果沒(méi)有調(diào)用 cancel() 的話(huà),計(jì)劃的任務(wù)是否還會(huì)運(yùn)行。更準(zhǔn)確地說(shuō),如果任務(wù)在調(diào)用 cancel() 之前是 SCHEDULED 狀態(tài),那么它就返回 true。如果試圖再次計(jì)劃一個(gè)取消的(甚至是已計(jì)劃的)任務(wù),那么 Scheduler 就會(huì)拋出一個(gè) IllegalStateException。

          取消計(jì)劃任務(wù)的第二種方式是讓 ScheduleIterator 返回 null。這只是第一種方式的簡(jiǎn)化操作,因?yàn)?Scheduler 類(lèi)調(diào)用 SchedulerTask 類(lèi)的 cancel()方法。如果您想用迭代器而不是任務(wù)來(lái)控制計(jì)劃停止時(shí)間時(shí),就用得上這種取消任務(wù)的方式了。

          第三種方式是通過(guò)調(diào)用其 cancel() 方法取消整個(gè) Scheduler。這會(huì)取消調(diào)試程序的所有任務(wù),并使它不能再計(jì)劃任何任務(wù)。

          擴(kuò)展 cron 實(shí)用程序

          可以將計(jì)劃框架比作 UNIX 的 cron 實(shí)用程序,只不過(guò)計(jì)劃次數(shù)的規(guī)定是強(qiáng)制性而不是聲明性的。例如,在 AlarmClock 實(shí)現(xiàn)中使用的 DailyIterator 類(lèi),它的計(jì)劃與 cron 作業(yè)的計(jì)劃相同,都是由以 0 7 * * * 開(kāi)始的 crontab 項(xiàng)指定的(這些字段分別指定分鐘、小時(shí)、日、月和星期)。

          不過(guò),計(jì)劃框架比 cron 更靈活。想像一個(gè)在早晨打開(kāi)熱水的 HeatingController 應(yīng)用程序。我想指示它“在每個(gè)工作日上午 8:00 打開(kāi)熱水,在周未上午 9:00 打開(kāi)熱水”。使用 cron,我需要兩個(gè) crontab 項(xiàng)(0 8 * * 1,2,3,4,5 和 0 9 * * 6,7)。而使用 ScheduleIterator 的解決方案更簡(jiǎn)潔一些,因?yàn)槲铱梢允褂脧?fù)合(composition)來(lái)定義單一迭代器。清單 7 顯示了其中的一種方法:

          清單 7. 用復(fù)合定義單一迭代器

          int[] weekdays = new int[] {
          Calendar.MONDAY,
          Calendar.TUESDAY,
          Calendar.WEDNESDAY,
          Calendar.THURSDAY,
          Calendar.FRIDAY
          };
          int[] weekend = new int[] {
          Calendar.SATURDAY,
          Calendar.SUNDAY
          };
          ScheduleIterator i = new CompositeIterator(
          new ScheduleIterator[] {
          new RestrictedDailyIterator(8, 0, 0, weekdays),
          new RestrictedDailyIterator(9, 0, 0, weekend)
          }
          );


          RestrictedDailyIterator 類(lèi)很像 DailyIterator,只不過(guò)它限制為只在一周的特定日子里運(yùn)行,而一個(gè) CompositeIterator 類(lèi)取得一組 ScheduleIterators,并將日期正確排列到單個(gè)計(jì)劃中。

          有許多計(jì)劃是 cron 無(wú)法生成的,但是 ScheduleIterator 實(shí)現(xiàn)卻可以。例如,“每個(gè)月的最后一天”描述的計(jì)劃可以用標(biāo)準(zhǔn) Java 日歷算法來(lái)實(shí)現(xiàn)(用 Calendar 類(lèi)),而用 cron 則無(wú)法表達(dá)它。應(yīng)用程序甚至無(wú)需使用 Calendar 類(lèi)。在本文的源代碼(請(qǐng)參閱 參考資料)中,我加入了一個(gè)安全燈控制器的例子,它按“在日落之前 15 分鐘開(kāi)燈”這一計(jì)劃運(yùn)行。這個(gè)實(shí)現(xiàn)使用了 Calendrical Calculations Software Package,用于計(jì)算當(dāng)?shù)兀ńo定經(jīng)度和緯度)的日落時(shí)間。

          實(shí)時(shí)保證

          在編寫(xiě)使用計(jì)劃的應(yīng)用程序時(shí),一定要了解框架在時(shí)間方面有什么保證。我的任務(wù)是提前還是延遲執(zhí)行?如果有提前或者延遲,偏差最大值是多少?不幸的是,對(duì)這些問(wèn)題沒(méi)有簡(jiǎn)單的答案。不過(guò)在實(shí)際中,它的行為對(duì)于很多應(yīng)用程序已經(jīng)足夠了。下面的討論假設(shè)系統(tǒng)時(shí)鐘是正確的。

          因?yàn)?Scheduler 將計(jì)劃委托給 Timer 類(lèi),Scheduler 可以做出的實(shí)時(shí)保證與 Timer 的一樣。Timer 用 Object.wait(long) 方法計(jì)劃任務(wù)。當(dāng)前線(xiàn)程要等待直到喚醒它,喚醒可能出于以下原因之一:

          1.另一個(gè)線(xiàn)程調(diào)用對(duì)象的 notify() 或者 notifyAll() 方法。

          2.線(xiàn)程被另一個(gè)線(xiàn)程中斷。

          3.在沒(méi)有通知的情況下,線(xiàn)程被喚醒(稱(chēng)為 spurious wakeup,Joshua Bloch 的 Effective Java Programming Language Guide 一書(shū)中 Item 50 對(duì)其進(jìn)行了描述 。

          4.規(guī)定的時(shí)間已到。

          對(duì)于 Timer 類(lèi)來(lái)說(shuō),第一種可能性是不會(huì)發(fā)生的,因?yàn)閷?duì)其調(diào)用 wait() 的對(duì)象是私有的。即便如此,Timer 實(shí)現(xiàn)仍然針對(duì)前三種提前喚醒的原因進(jìn)行了保護(hù),這樣保證了線(xiàn)程在規(guī)定時(shí)間后才喚醒。目前,Object.wait(long) 的文檔注釋聲明,它會(huì)在規(guī)定的時(shí)間“前后”蘇醒,所以線(xiàn)程有可能提前喚醒。在本例中,Timer 會(huì)讓另一個(gè) wait() 執(zhí)行(scheduledExecutionTime - System.currentTimeMillis())毫秒,從而保證任務(wù)永遠(yuǎn)不會(huì)提前執(zhí)行。任務(wù)是否會(huì)延遲執(zhí)行呢?會(huì)的。延遲執(zhí)行有兩個(gè)主要原因:線(xiàn) 程計(jì)劃和垃圾收集。

          Java 語(yǔ)言規(guī)范故意沒(méi)有對(duì)線(xiàn)程計(jì)劃做嚴(yán)格的規(guī)定。這是因?yàn)?Java 平臺(tái)是通用的,并針對(duì)于大范圍的硬件及其相關(guān)的操作系統(tǒng)。雖然大多數(shù) JVM 實(shí)現(xiàn)都有公平的線(xiàn)程調(diào)度程序,但是這一點(diǎn)沒(méi)有任何保證 —— 當(dāng)然,各個(gè)實(shí)現(xiàn)都有不同的為線(xiàn)程分配處理器時(shí)間的策略。因此,當(dāng) Timer 線(xiàn)程在分配的時(shí)間后喚醒時(shí),它實(shí)際執(zhí)行其任務(wù)的時(shí)間取決于 JVM 的線(xiàn)程計(jì)劃策略,以及有多少其他線(xiàn)程競(jìng)爭(zhēng)處理器時(shí)間。因此,要減緩任務(wù)的延遲執(zhí)行,應(yīng)該將應(yīng)用程序中可運(yùn)行的線(xiàn)程數(shù)降至最少。為了做到這一點(diǎn),可以考慮在 一個(gè)單獨(dú)的 JVM 中運(yùn)行調(diào)度程序。

          對(duì)于創(chuàng)建大量對(duì)象的大型應(yīng)用程序,JVM 花在垃圾收集(GC)上的時(shí)間會(huì)非常多。默認(rèn)情況下,進(jìn)行 GC 時(shí),整個(gè)應(yīng)用程序都必須等待它完成,這可能要有幾秒鐘甚至更長(zhǎng)的時(shí)間(Java 應(yīng)用程序啟動(dòng)器的命令行選項(xiàng) -verbose:gc 將導(dǎo)致向控制臺(tái)報(bào)告每一次 GC 事件)。要將這些由 GC 引起的暫停(這可能會(huì)影響快速任務(wù)的執(zhí)行)降至最少,應(yīng)該將應(yīng)用程序創(chuàng)建的對(duì)象的數(shù)目降至最低。同樣,在單獨(dú)的 JVM 中運(yùn)行計(jì)劃代碼是有幫助的。同時(shí),可以試用幾個(gè)微調(diào)選項(xiàng)以盡可能地減少 GC 暫停。例如,增量 GC 會(huì)盡量將主收集的代價(jià)分散到幾個(gè)小的收集上。當(dāng)然這會(huì)降低 GC 的效率,但是這可能是時(shí)間計(jì)劃的一個(gè)可接受的代價(jià)。

          被計(jì)劃到什么時(shí)候?

          如果任務(wù)本身能監(jiān)視并記錄所有延遲執(zhí)行的實(shí)例,那么對(duì)于確定任務(wù)是否能按時(shí)運(yùn)行會(huì)很有幫助。SchedulerTask 類(lèi)似于 TimerTask,有一個(gè) scheduledExecutionTime() 方法,它返回計(jì)劃任務(wù)最近一次執(zhí)行的時(shí)間。在任務(wù)的 run() 方法開(kāi)始時(shí),對(duì)表達(dá)式 System.currentTimeMillis() - scheduledExecutionTime() 進(jìn)行判斷,可以讓您確定任務(wù)延遲了多久執(zhí)行(以毫秒為單位)。可以記錄這個(gè)值,以便生成一個(gè)關(guān)于延遲執(zhí)行的分布統(tǒng)計(jì)。可以用這個(gè)值決定任務(wù)應(yīng)當(dāng)采取什么動(dòng) 作 —— 例如,如果任務(wù)太遲了,那么它可能什么也不做。在遵循上述原則的情況下,如果應(yīng)用程序需要更嚴(yán)格的時(shí)間保證,可參考 Java 的實(shí)時(shí)規(guī)范。

          結(jié)束語(yǔ)

          在本文中,我介紹了 Java 定時(shí)器框架的一個(gè)簡(jiǎn)單增強(qiáng),它使得靈活的計(jì)劃策略成為可能。新的框架實(shí)質(zhì)上是更通用的 cron —— 事實(shí)上,將 cron 實(shí)現(xiàn)為一個(gè) ScheduleIterator 接口,用以替換單純的 Java cron,這是非常有用的。雖然沒(méi)有提供嚴(yán)格的實(shí)時(shí)保證,但是許多需要計(jì)劃定期任務(wù)的通用 Java 應(yīng)用程序都可以使用這一框架。

          參考資料

          ·下載本文中使用的 源代碼。

          ·“Tuning Garbage Collection with the 1.3.1 Java Virtual Machine”是 Sun 的一篇非常有用的文章,它給出了關(guān)于如何最小化 GC 暫停時(shí)間的提示。

          ·要獲得 developerWorks 中有關(guān) GC 的更多信息,請(qǐng)參閱以下文章:

          Java 理論與實(shí)踐:垃圾收集簡(jiǎn)史” (2003 年 10 月)。

          Mash that trash”(2003 年 7 月)。

          Fine-tuning Java garbage collection performance”(2003 年 1 月)。

          Sensible sanitation, Part 1”(2002 年 8 月)。

          Sensible sanitation, Part 2”(2002 年 8 月)。

          Sensible sanitation, Part 3”(2002 年 9 月)。

          ·在“Java 理論與實(shí)踐:并發(fā)在一定程度上使一切變得簡(jiǎn)單”(developerWorks, 2002 年 11 月)中,Brian Goetz 討論了 Doug Lea 的 util.concurrent 庫(kù),這是一個(gè)并發(fā)實(shí)用工具類(lèi)的寶庫(kù)。

          ·Brian Goetz 的另一篇文章“Threading lightly, Part 2: Reducing contention”(developerWorks,2001 年 9 月)分析了線(xiàn)程競(jìng)用以及如何減少它。

          關(guān)于作者

          Tom White 是 Kizoom 的首席 Java 開(kāi)發(fā)人員,Kizoom 是一家領(lǐng)先的英國(guó)軟件公司,提供向移動(dòng)設(shè)備發(fā)送個(gè)性化旅行信息的服務(wù)。客戶(hù)包括英國(guó)的國(guó)家火車(chē)操作員、倫敦公共交通系統(tǒng)(national train operator),以及英國(guó)國(guó)家公共汽車(chē)公司。自 1999 年成立以來(lái),Kizoom 使用了極限編程的所有方法。自 1996 年起,Tom 一直全職編寫(xiě) Java 程序,使用了大部分標(biāo)準(zhǔn)和企業(yè) Java API,編寫(xiě)了從客戶(hù) Swing GUI 和圖形到后端消息傳送系統(tǒng)等各種應(yīng)用程序。他在劍橋大學(xué)獲得了一級(jí)榮譽(yù)學(xué)位(first class honours degree)。工作之余,Tom 喜歡逗他的小女兒開(kāi)心,觀(guān)看 20 世紀(jì) 30 年代的好萊塢電影。可以通過(guò) tom@tiling.org 與 Tom 聯(lián)系。
          posted on 2005-08-31 15:15 笨笨 閱讀(3423) 評(píng)論(0)  編輯  收藏 所屬分類(lèi): J2EEALL
          主站蜘蛛池模板: 铜陵市| 荣成市| 新龙县| 噶尔县| 启东市| 富顺县| 赞皇县| 永定县| 株洲市| 太和县| 商河县| 黑河市| 汤原县| 高陵县| 松滋市| 徐水县| 正宁县| 铜鼓县| 女性| 泌阳县| 通渭县| 苏尼特左旗| 缙云县| 宾川县| 克拉玛依市| 彭阳县| 四平市| 阳春市| 施秉县| 视频| 尚志市| 贵州省| 弥渡县| 东兰县| 九龙坡区| 皋兰县| 长阳| 仪陇县| 襄城县| 黄浦区| 泰和县|