App下載

在 Java 應(yīng)用程序中安排重復(fù)性任務(wù)

愛(ài)嘯的女孩超愛(ài)看你笑 2021-09-13 11:58:23 瀏覽數(shù) (2514)
反饋

?java.util.Timer?和?java.util.TimerTask?類(我將二者統(tǒng)稱為Java的定時(shí)器框架)使得程序員可以輕松地安排簡(jiǎn)單的任務(wù)。(請(qǐng)注意,這些類在 J2ME 中也可用。)在 Java 2 SDK 標(biāo)準(zhǔn)版 1.3 版中引入此框架之前,開(kāi)發(fā)人員必須編寫(xiě)自己的調(diào)度程序,這涉及處理線程和?Object.wait()?方法的復(fù)雜性。但是,Java 定時(shí)器框架不夠豐富,無(wú)法滿足許多應(yīng)用程序的調(diào)度需求。即使是需要每天同時(shí)重復(fù)的任務(wù)也不能直接使用?Timer?進(jìn)行調(diào)度,因?yàn)橄牧顣r(shí)的來(lái)來(lái)往往會(huì)發(fā)生時(shí)間跳躍。

本文介紹了一個(gè)調(diào)度框架,它是對(duì)Timer?和?TimerTask?的推廣,允許更靈活的調(diào)度。該框架非常簡(jiǎn)單——它由兩個(gè)類和一個(gè)接口組成——而且很容易學(xué)習(xí)。如果你習(xí)慣于使用 Java 計(jì)時(shí)器框架,那么你應(yīng)該能夠很快掌握調(diào)度框架。)

安排一次性任務(wù)

調(diào)度框架構(gòu)建在 Java 計(jì)時(shí)器框架類之上。因此,在解釋調(diào)度框架的使用方式和實(shí)現(xiàn)方式之前,我們將首先了解如何使用這些類進(jìn)行調(diào)度。

想象一個(gè)雞蛋計(jì)時(shí)器,它通過(guò)播放聲音告訴您何時(shí)過(guò)去了數(shù)分鐘(因此你的雞蛋已煮熟)。清單 1 中的代碼構(gòu)成了用 Java 語(yǔ)言編寫(xiě)的簡(jiǎn)單的雞蛋計(jì)時(shí)器的基礎(chǔ):

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

}

一個(gè)?EggTimer?實(shí)例擁有一個(gè)?Timer?實(shí)例來(lái)提供必要的調(diào)度。當(dāng)使用?start()?方法啟動(dòng)雞蛋計(jì)時(shí)器時(shí),它會(huì)安排? aTimerTask?在指定的分鐘數(shù)后執(zhí)行。當(dāng)時(shí)間到時(shí), 上的?run()?方法?TimerTask?由?Timer?幕后調(diào)用,使其播放聲音。然后應(yīng)用程序在定時(shí)器被取消后終止。

安排重復(fù)性任務(wù)

Timer允許通過(guò)指定固定的執(zhí)行速率或執(zhí)行之間的固定延遲來(lái)安排任務(wù)重復(fù)執(zhí)行。但是,有許多應(yīng)用程序具有更復(fù)雜的調(diào)度要求。例如,每天早上在同一時(shí)間響起叫醒電話的鬧鐘不能簡(jiǎn)單地使用 86400000 毫秒(24 小時(shí))的固定速率時(shí)間表,因?yàn)樵跁r(shí)鐘前進(jìn)的日子里,鬧鐘會(huì)太晚或太早或向后(如果您的時(shí)區(qū)使用夏令時(shí))。解決方案是使用日歷算法來(lái)計(jì)算每日事件的下一次預(yù)定發(fā)生。這正是調(diào)度框架所支持的。考慮AlarmClock 清單 2 中的實(shí)現(xiàn)(請(qǐng)參閱相關(guān)鏈接以下載調(diào)度框架的源代碼,以及包含框架和示例的 JAR 文件):

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

請(qǐng)注意代碼與雞蛋計(jì)時(shí)器應(yīng)用程序的相似程度。AlarmClock實(shí)例擁有Scheduler實(shí)例(而不是一個(gè)Timer)提供必要的調(diào)度。啟動(dòng)時(shí),鬧鐘會(huì)安排 a SchedulerTask(而不是 a TimerTask)來(lái)播放鬧鐘。而不是在固定延遲后安排任務(wù)執(zhí)行,鬧鐘使用一個(gè)DailyIterator類來(lái)描述它的時(shí)間表。在這種情況下,它只是在每天早上 7:00 安排任務(wù)。這是典型運(yùn)行的輸出:

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

??DailyIterator?實(shí)現(xiàn)?ScheduleIterator??接口,該接口將?SchedulerTask?的計(jì)劃執(zhí)行時(shí)間指定為一系列?java.util.Date?對(duì)象。??然后,?next()?方法?按時(shí)間順序迭代對(duì)象。返回值?null?會(huì)導(dǎo)致任務(wù)被取消(也就是說(shuō),它永遠(yuǎn)不會(huì)再次運(yùn)行)——實(shí)際上,重新調(diào)度的嘗試將導(dǎo)致拋出異常。清單 3 包含ScheduleIterator接口:

清單 3. ScheduleIterator 接口
package org.tiling.scheduling;

import java.util.Date;

public interface ScheduleIterator {
    public Date next();
}

DailyIterator的?next()?方法返回Date表示每天同一時(shí)間(上午 7:00)的對(duì)象,如清單 4 所示。因此,如果你調(diào)用?next()?一個(gè)新構(gòu)造的DailyIterator類,您將獲得該日期當(dāng)天或之后的當(dāng)天上午 7:00傳入構(gòu)造函數(shù)。隨后的調(diào)用?next()?將在隨后幾天的上午 7:00 返回,并永遠(yuǎn)重復(fù)。要實(shí)現(xiàn)此行為,請(qǐng)DailyIterator使用java.util.Calendar實(shí)例。構(gòu)造函數(shù)設(shè)置日歷,以便第一次調(diào)用next()返回正確的,Date只需在日歷上添加一天。請(qǐng)注意,該代碼沒(méi)有明確提及夏令時(shí)修正;它不需要,因?yàn)镃alendar實(shí)現(xiàn)(在這種情況下GregorianCalendar)會(huì)處理這個(gè)問(wèn)題。

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

}

實(shí)現(xiàn)調(diào)度框架

在上一節(jié)中,我們學(xué)習(xí)了如何使用調(diào)度框架,并將其與 Java 定時(shí)器框架進(jìn)行了比較。接下來(lái),我將向你展示該框架是如何實(shí)現(xiàn)的。除了?ScheduleIterator?在顯示界面清單3中,還有另外兩個(gè)類-?Scheduler?和?SchedulerTask?-組成的框架。這些類實(shí)際上在封面下使用?Timer?和?TimerTask?,因?yàn)槿粘虒?shí)際上只不過(guò)是一個(gè)系列的一次性計(jì)時(shí)器。清單 5 和 6 顯示了這兩個(gè)類的源代碼:

清單 5. 調(diào)度程序
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該類的源代碼:

清單 6. SchedulerTask
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();
        }
    }

}

就像雞蛋定時(shí)器一樣,調(diào)度器的每個(gè)實(shí)例都擁有一個(gè)計(jì)時(shí)器的實(shí)例,以提供底層調(diào)度。與用于實(shí)現(xiàn)雞蛋計(jì)時(shí)器的單一一次性計(jì)時(shí)器不同,調(diào)度器將一次性計(jì)時(shí)器串連在一起,以?ScheduleIterator?指定的時(shí)間執(zhí)行?SchedulerTask?類。

考慮調(diào)度器公共的?schedule()?方法——這是調(diào)度的入口點(diǎn),因?yàn)樗强蛻舳苏{(diào)用的方法。(唯一的其他公共方法?cancel()?,在?Canceling tasks?中介紹。)所述的第一次執(zhí)行的時(shí)間SchedulerTask,通過(guò)調(diào)用?ScheduleIterator?接口上的?next()?方法。然后通過(guò)調(diào)用底層Timer類上的?one-shot schedule()?方法啟動(dòng)調(diào)度,一邊在此時(shí)執(zhí)行。為一次性執(zhí)行提供的?TimerTask?對(duì)象是嵌套?SchedulerTimerTask?類的一個(gè)實(shí)例,它打包了任務(wù)和迭代器。在分配的時(shí)間內(nèi),?run()?方法在嵌套類上調(diào)用,它使用打包的任務(wù)和迭代器引用來(lái)重新安排任務(wù)的下一次執(zhí)行。?reschedule()?方法與?schedule()?方法非常相似,不同之處在于它是私有的,并且對(duì)?SchedulerTask?執(zhí)行一組略有不同的狀態(tài)檢查。重新調(diào)度過(guò)程無(wú)限重復(fù),為每次調(diào)度的執(zhí)行構(gòu)造一個(gè)新的嵌套類實(shí)例,直到任務(wù)或調(diào)度程序被取消(或 JVM 關(guān)閉)。

與對(duì)應(yīng)的?TimerTask?一樣,?SchedulerTask?在其生命周期中經(jīng)歷一系列狀態(tài)。創(chuàng)建時(shí),它處于一種?VIRGIN?狀態(tài),這意味著它從未被調(diào)度過(guò)。一旦被調(diào)度,它就會(huì)轉(zhuǎn)移到一個(gè)?SCHEDULED?狀態(tài),如果任務(wù)被下面描述的方法之一取消,則之后會(huì)切換到到?CANCELLED?狀態(tài)。管理正確的狀態(tài)轉(zhuǎn)換,例如確保非?VIRGIN?任務(wù)不會(huì)被調(diào)度兩次,會(huì)增加?Scheduler?和?SchedulerTask?類的額外復(fù)雜性。每當(dāng)執(zhí)行可能改變?nèi)蝿?wù)狀態(tài)的操作時(shí),代碼必須在任務(wù)的鎖定對(duì)象上同步。

取消任務(wù)

取消計(jì)劃任務(wù)的方式有三種。第一種是調(diào)用?SchedulerTask?上的?cancel()?方法。這就像?TimerTask?上調(diào)用?cancel()? :該任務(wù)將永遠(yuǎn)不會(huì)再次運(yùn)行,盡管如果已經(jīng)運(yùn)行,它將一直運(yùn)行到完成。?cancel()方法?的返回值是是一個(gè)布爾值,用來(lái)指示在尚未被調(diào)用?cancel()?的情況下是否會(huì)運(yùn)行進(jìn)一步的計(jì)劃任務(wù)。更準(zhǔn)確地說(shuō),如果任務(wù)在調(diào)用?cancel()?之前立即處于?SCHEDULED?狀態(tài),它就會(huì)返回?true?。如果你嘗試重新安排已取消(甚至已安排)的任務(wù),?Scheduler?則會(huì)拋出?IllegalStateException?.

取消計(jì)劃任務(wù)的第二種方法是?ScheduleIterator?返回?null?。這只是第一種方式的快捷方式,因?yàn)?Scheduler?類調(diào)用??SchedulerTask?類上的cancel()?。如果你希望迭代器(而不是任務(wù))控制調(diào)度何時(shí)停止,則以這種方式取消任務(wù)很有用。

第三種方式是通過(guò)調(diào)用它的?cancel()?方法來(lái)取消整體的?Scheduler?。這將取消調(diào)度程序的所有任務(wù),并使其處于不能再調(diào)度更多任務(wù)的狀態(tài)。

擴(kuò)展 cron 工具

調(diào)度框架可以比作 UNIX cron工具,除了調(diào)度時(shí)間的規(guī)范是命令式控制而不是聲明式控制。例如,?DailyIterator?類在?AlarmClock?實(shí)現(xiàn)中使用與?cron?作業(yè)具有相同的調(diào)度,由?0 7 * * *?開(kāi)始的?crontab?條目指定。(這些字段分別指定分鐘、小時(shí)、月中的某一天、月份和星期幾。)

但是,調(diào)度框架比cron具有更強(qiáng)大的靈活性。 想象一個(gè)?HeatingController?應(yīng)用程序在早上打開(kāi)熱水。我想指示它“在工作日的早上 8:00 和周末早上 9:00 打開(kāi)熱水”。使用cron,我需要兩個(gè)?crontab?條目(?0 8 * * 1,2,3,4,5?和?0 9 * * 6,7?)。通過(guò)使用 ?ScheduleIterator?,解決方案更加優(yōu)雅,因?yàn)槲铱梢允褂媒M合定義單個(gè)迭代器。清單 7 顯示了一種方法:

清單 7. 使用組合定義單個(gè)迭代器
    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)
        }
    );

一個(gè)?RestrictedDailyIterator?類就像?DailyIterator?,除了它被限制在一周的特定日期運(yùn)行;并且一個(gè)?CompositeIterator?類采用一組?ScheduleIterators ?并將日期正確地排序到一個(gè)單一的時(shí)間表中。

還有很多其他的調(diào)度cron不能產(chǎn)生,但是可以實(shí)現(xiàn)?ScheduleIterator?。例如,“每個(gè)月的最后一天”描述的日程安排可以使用標(biāo)準(zhǔn) Java 日歷算法(使用Calendar類)來(lái)實(shí)現(xiàn),而使用cron. 應(yīng)用程序甚至不必使用Calendar該類。在本文的源代碼中,我提供了一個(gè)安全燈控制器示例,該控制器按照“在日落前 15 分鐘開(kāi)燈”的時(shí)間表運(yùn)行。該實(shí)現(xiàn)使用 Calendrical Calculations 軟件包,計(jì)算本地日落時(shí)間(給定緯度和經(jīng)度)。

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

在編寫(xiě)使用調(diào)度的應(yīng)用程序時(shí),重要的是要了解框架在及時(shí)性方面的承諾。我的任務(wù)會(huì)提前還是推遲執(zhí)行?如果是這樣,最大誤差幅度是多少?不幸的是,這些問(wèn)題沒(méi)有簡(jiǎn)單的答案。然而,在實(shí)踐中,該行為對(duì)于一大類應(yīng)用程序來(lái)說(shuō)已經(jīng)足夠好了。下面的討論假設(shè)系統(tǒng)時(shí)鐘是正確的(有關(guān)網(wǎng)絡(luò)時(shí)間協(xié)議的信息,請(qǐng)參閱相關(guān)鏈接)。

因?yàn)镾cheduler將其調(diào)度委托給Timer類,所以Scheduler可以做出的實(shí)時(shí)保證與Timer. Timer使用該Object.wait(long)方法調(diào)度任務(wù)。當(dāng)前線程等待直到被喚醒,這可能是由于以下原因之一:

  1. 所述?notify()?或?notifyAll()?方法被稱為通過(guò)另一個(gè)線程的對(duì)象。
  2. 該線程被另一個(gè)線程中斷。
  3. 該線程在沒(méi)有通知的情況下被喚醒。(虛假喚醒)
  4. 指定的時(shí)間已經(jīng)過(guò)去。

第一種可能性不會(huì)發(fā)生在Timer類上,因?yàn)?wait()?被調(diào)用的對(duì)象是私有的。即便如此,Timer對(duì)前三個(gè)原因的提前喚醒實(shí)施了保障措施,從而確保線程在時(shí)間過(guò)去后喚醒?,F(xiàn)在,文檔注釋?Object.wait(long)?指出它可能會(huì)在“或多或少”時(shí)間過(guò)去后喚醒,因此線程可能會(huì)提前喚醒。在這種情況下,Timer發(fā)出另一個(gè)?wait()?為?(scheduledExecutionTime - System.currentTimeMillis())?毫秒,從而保證任務(wù)永遠(yuǎn)不能被早期執(zhí)行。

任務(wù)可以延遲執(zhí)行嗎?是的。延遲執(zhí)行的主要原因有兩個(gè):線程調(diào)度和垃圾收集。

Java 語(yǔ)言規(guī)范在線程調(diào)度上故意含糊其辭。這是因?yàn)?Java 平臺(tái)是通用的,面向廣泛的硬件和相關(guān)操作系統(tǒng)。雖然大多數(shù) JVM 實(shí)現(xiàn)都有一個(gè)公平的線程調(diào)度程序,但這并不能保證——當(dāng)然,實(shí)現(xiàn)有不同的策略來(lái)為線程分配處理器時(shí)間。因此,當(dāng)一個(gè)Timer線程在其分配的時(shí)間后喚醒時(shí),它實(shí)際執(zhí)行任務(wù)的時(shí)間取決于 JVM 的線程調(diào)度策略,以及有多少其他線程在爭(zhēng)用處理器時(shí)間。因此,為了減少延遲任務(wù)執(zhí)行,您應(yīng)該最大限度地減少應(yīng)用程序中可運(yùn)行線程的數(shù)量。值得考慮在單獨(dú)的 JVM 中運(yùn)行調(diào)度程序來(lái)實(shí)現(xiàn)這一點(diǎn)。

JVM 執(zhí)行垃圾收集 (GC) 所花費(fèi)的時(shí)間對(duì)于創(chuàng)建大量對(duì)象的大型應(yīng)用程序來(lái)說(shuō)可能很重要。默認(rèn)情況下,當(dāng) GC 發(fā)生時(shí),整個(gè)應(yīng)用程序必須等待它完成,這可能需要幾秒鐘或更長(zhǎng)時(shí)間。(命令行選項(xiàng)-verbose:gc的java應(yīng)用程序啟動(dòng)器將導(dǎo)致每個(gè) GC 事件都報(bào)告到控制臺(tái)。)為了盡量減少由于 GC 引起的暫停,這可能會(huì)阻礙即時(shí)任務(wù)執(zhí)行,您應(yīng)該盡量減少應(yīng)用程序創(chuàng)建的對(duì)象數(shù)量。同樣,這有助于在單獨(dú)的 JVM 中運(yùn)行您的調(diào)度代碼。此外,您可以嘗試使用多種優(yōu)化選項(xiàng)來(lái)最小化 GC 暫停。例如,增量 GC 試圖將主要收集的成本分散到幾個(gè)次要收集上。代價(jià)是這會(huì)降低 GC 的效率,但對(duì)于更及時(shí)的調(diào)度來(lái)說(shuō),這可能是一個(gè)可以接受的代價(jià)。

什么時(shí)候調(diào)用

為了確定任務(wù)是否正在及時(shí)運(yùn)行,如果任務(wù)本身監(jiān)視和記錄任何延遲執(zhí)行的實(shí)例會(huì)有所幫助。SchedulerTask,如TimerTask,有一個(gè)?scheduledExecutionTime()?方法返回最近一次執(zhí)行此任務(wù)的時(shí)間。評(píng)估?System.currentTimeMillis()? - ?scheduledExecutionTime()?任務(wù)開(kāi)始時(shí)的表達(dá)式run()方法可讓您確定任務(wù)執(zhí)行的延遲時(shí)間(以毫秒為單位)??梢杂涗洿酥狄陨捎嘘P(guān)延遲執(zhí)行分布的統(tǒng)計(jì)信息。該值還可用于決定任務(wù)應(yīng)該采取什么操作——例如,如果任務(wù)太晚,它可能什么都不做。如果在遵循上述指南后, 你的應(yīng)用程序需要更嚴(yán)格的及時(shí)性保證,請(qǐng)考慮查看 Java 實(shí)時(shí)規(guī)范。

結(jié)論

在本文中,我介紹了對(duì) Java 計(jì)時(shí)器框架的簡(jiǎn)單增強(qiáng),它允許非常靈活的調(diào)度策略。新框架本質(zhì)上是對(duì)cron——事實(shí)上,cron作為一個(gè)?ScheduleIterator?接口來(lái)實(shí)現(xiàn)以提供純 Javacron替代品是有價(jià)值的。雖然不提供嚴(yán)格的實(shí)時(shí)保證,但該框架適用于需要定期調(diào)度任務(wù)的大量通用 Java 應(yīng)用程序。


0 人點(diǎn)贊