Java定時任務之Timer

Timer是Java中實現定時任務的方式之一,下面是一個簡單的例子:java

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

public class TimerStudy {

  public static void main(String[] argc) {
    Timer timer = new Timer();
    TimerTask timerTask = new TimerTask() {
      @Override public void run() {
        System.out.println("Hello, world!");
      }
    };
    timer.scheduleAtFixedRate(timerTask, 30_000, 30_000);
  }
}

從代碼中能夠看出,使用Timer實現定時任務,涉及到兩個類:Timer和TimerTask,下面從源碼來看看Timer和TimerTask的實現細節。api

TimerTask源碼以下:數組

public abstract class TimerTask implements Runnable {

    final Object lock = new Object();

    int state = VIRGIN;

    static final int VIRGIN = 0;

    static final int SCHEDULED   = 1;

    static final int EXECUTED    = 2;

    static final int CANCELLED   = 3;

    long nextExecutionTime;

    long period = 0;

    protected TimerTask() {
    }

    public abstract void run();

    public boolean cancel() {
        synchronized(lock) {
            boolean result = (state == SCHEDULED);
            state = CANCELLED;
            return result;
        }
    }

    public long scheduledExecutionTime() {
        synchronized(lock) {
            return (period < 0 ? nextExecutionTime + period
                               : nextExecutionTime - period);
        }
    }
}

從源碼中能夠看出,TimerTask實現了Runnable接口,所以定義TimerTask對象時,須要實現run方法來實現本身的任務。ide

TimerTask類有三個成員變量state, nextExecutionTime和period, 其中state表示定時任務的狀態,取值範圍以下:oop

  • VIRGIN(0): 表示定時任務尚未被調度執行,新建的定時任務默認爲VIRGIN狀態;
  • SCHEDULED(1): 表示定時任務已經被調度;
  • EXECUTED(2): 表示定時任務已經執行,或者正在執行,注意:只有非重複執行的定時任務纔有此狀態
  • CANCELLED(3): 表示定時任務已經被取消了,調用TimerTask.cancel()方法能夠取消定時任務

nextExecutionTime表示定時任務下次執行的時間。學習

period表示定時任務兩次執行的間隔,=0表示不須要重複執行,>0表示固定頻率執行,<0表示固定延遲執行;固定頻率和固定延遲的主要區別在於:固定頻率下一次計劃執行時間是按照上一次計劃執行時間加上延遲計算的;而固定延遲是按照上次實際執行時間加上延遲來計算的。舉個例子:假如定時任務A計劃執行時間是01:00:00, 實際執行時間是01:02:00, 延遲是10分鐘,那固定頻率的下一次計劃執行時間是01:10:00, 而固定延遲的下一次計劃執行時間是01:12:00. ui

TimeTask主要的成員方法有cancel和scheduledExecutionTime, cancel方法用來取消定時任務,scheduledExecutionTime返回下一次計劃執行時間。spa

scheduledExecution方法的代碼以下:線程

public long scheduledExecutionTime() {
        synchronized(lock) {
            return (period < 0 ? nextExecutionTime + period
                               : nextExecutionTime - period);
        }
    }

該方法主要用來在run方法中調用,用來判判定時任務執行的是否及時;因爲固定延遲的定時任務的執行時間能夠日後偏移,所以固定延遲執行的定時任務調用次方法沒有任何實際意義。對象

下面來看看Timer類的定義:

public class Timer {
    /**
     * 定時任務隊列,經過Timer添加的TimerTask都由TaskQueue管理
     */
    private final TaskQueue queue = new TaskQueue();

    /**
     * 定時任務調度和執行線程
     */
    private final TimerThread thread = new TimerThread(queue);

    /**
     * 當TaskQueue中沒有任務,且沒有引用引用到Timer對象時,該對象可使TimerThread優雅的退出
     */
    private final Object threadReaper = new Object() {
        protected void finalize() throws Throwable {
            synchronized(queue) {
                thread.newTasksMayBeScheduled = false;
                queue.notify(); // In case queue is empty.
            }
        }
    };

    /**
     * 用於生成TimerThread的名字
     */
    private final static AtomicInteger nextSerialNumber = new AtomicInteger(0);
    private static int serialNumber() {
        return nextSerialNumber.getAndIncrement();
    }

    public Timer();

    public Timer(boolean isDaemon);

    public Timer(String name);

    public Timer(String name, boolean isDaemon);

    public void schedule(TimerTask task, long delay);

    public void schedule(TimerTask task, Date time);

    public void schedule(TimerTask task, long delay, long period);

    public void schedule(TimerTask task, Date firstTime, long period);

    public void scheduleAtFixedRate(TimerTask task, long delay, long period);

    public void scheduleAtFixedRate(TimerTask task, Date firstTime, long period);

    private void sched(TimerTask task, long time, long period);

    public void cancel();

     public int purge();
}

Timer類中主要的成員變量有兩個:一個是TaskQueue類型的queue,用來存放定時任務TimerTask;另一個是TimerThread類型的thread,用來進行定時任務的調度和執行。

Timer中添加定時任務的方法主要有兩類,schedule和scheduleAtFixedRate, schedule方法添加的是固定延遲執行的定時任務;而scheduleAtFixedRate方法添加的是固定頻率執行的定時任務,從下面的源碼中能夠看出主要區別:

public void schedule(TimerTask task, long delay, long period) {
        if (delay < 0)
            throw new IllegalArgumentException("Negative delay.");
        if (period <= 0)
            throw new IllegalArgumentException("Non-positive period.");
        //注意:period傳的是負值
        sched(task, System.currentTimeMillis()+delay, -period);
}
public void scheduleAtFixedRate(TimerTask task, long delay, long period) {
        if (delay < 0)
            throw new IllegalArgumentException("Negative delay.");
        if (period <= 0)
            throw new IllegalArgumentException("Non-positive period.");
        //注意:period傳的是正值
        sched(task, System.currentTimeMillis()+delay, period);
}

schedule方法調用sched方法是,period傳的負值,scheduleAtFixedRate方法調用sched方法時傳的period是正值,正好對應前面所講的固定速率和固定延遲的定時任務的區別。

sched方法只添加定時任務的主要邏輯所在,代碼以下:

private void sched(TimerTask task, long time, long period) {
    if (time < 0)
        throw new IllegalArgumentException("Illegal execution time.");

    // Constrain value of period sufficiently to prevent numeric
    // overflow while still being effectively infinitely large.
    if (Math.abs(period) > (Long.MAX_VALUE >> 1))
        period >>= 1;

    synchronized(queue) {
        if (!thread.newTasksMayBeScheduled)
            throw new IllegalStateException("Timer already cancelled.");

        synchronized(task.lock) {
            if (task.state != TimerTask.VIRGIN)
                throw new IllegalStateException(
                    "Task already scheduled or cancelled");
            task.nextExecutionTime = time;
            task.period = period;
            task.state = TimerTask.SCHEDULED;
        }

        queue.add(task);
        if (queue.getMin() == task)
            queue.notify();
    }
}

sched方法首先檢查了TimerThread的newTasksMayBeScheduled,若是Timer已經被取消則判處異常。而後設置task的nextExecutionTime和period,並將task的狀態修改成SCHEDULED. 而後將task添加到TaskQueue中,若是task是TaskQueue中第一個須要執行的任務,則調用queue.notify方法通知TaskThread,至於爲何須要調用notify方法,等學習TaskThread源碼時再具體看看。

cancel方法用來取消全部的定時任務,源碼以下:

public void cancel() {
    synchronized(queue) {
        thread.newTasksMayBeScheduled = false;
        queue.clear();
        queue.notify();  // In case queue was already empty.
    }
}

purge用來清理全部被取消的定時任務,源碼以下:

public int purge() {
    int result = 0;

    synchronized(queue) {
     for (int i = queue.size(); i > 0; i--) {
         if (queue.get(i).state == TimerTask.CANCELLED) {
             queue.quickRemove(i);
             result++;
         }
     }

     if (result != 0)
         queue.heapify();
    }

    return result;
}

下面來看會下TaskQueue的源碼:

class TaskQueue {

    private TimerTask[] queue = new TimerTask[128];

    private int size = 0;

    int size();

    void add(TimerTask task);

    TimerTask getMin();

    TimerTask get(int i);

    void removeMin();

    void quickRemove(int i);

    void rescheduleMin(long newTime);

    boolean isEmpty();

    void clear();

    private void fixUp(int k);

    private void fixDown(int k);

    void heapify();
}

TaskQueue用數組類存放TimerTask,並按照nextExecutionTime的大小構建最小堆,這樣堆頂的就是執行時間最先的定時任務。

TimerThread的源碼以下:

class TimerThread extends Thread {
    //設置爲false,通知線程已經沒有活着的引用執行Timer對象,使得TimerThread能夠很優雅地退出
    boolean newTasksMayBeScheduled = true;

    //引用的是Timer中的queue對象
    private TaskQueue queue;

    TimerThread(TaskQueue queue);

    //調用mainLoop,實現定時任務調度和執行
    public void run();

    //具體的定時任務調度和執行邏輯
    private void mainLoop();
}

TimerThread繼承了Thread類,主要的邏輯在mainLoop方法中,mainLoop方法源碼以下:

private void mainLoop() {
    while (true) {
        try {
            TimerTask task;
            boolean taskFired;
            synchronized(queue) {
                // 若是任務隊列爲空,就等待,直到其它線程添加任務時,調用notify方法喚醒TimerThread
                while (queue.isEmpty() && newTasksMayBeScheduled)
                    queue.wait();
                if (queue.isEmpty())
                    break; // 此時隊列爲空,說明newTaskMayBeScheduled=false,已經不存在活着的引用執行Timer,因此TimerTask須要結束調度和執行定時任務

                // Queue nonempty; look at first evt and do the right thing
                long currentTime, executionTime;
                task = queue.getMin();
                synchronized(task.lock) {
                    if (task.state == TimerTask.CANCELLED) {
                        queue.removeMin();
                        continue;  // 定時任務已經被取消,則刪除定時任務,而後從新執行循環
                    }
                    currentTime = System.currentTimeMillis();
                    executionTime = task.nextExecutionTime;
                    if (taskFired = (executionTime<=currentTime)) {
                        if (task.period == 0) { // 非重複執行的定時任務,從隊列中刪除
                            queue.removeMin();
                            task.state = TimerTask.EXECUTED;
                        } else { // 須要重複執行的定時任務,計算下一次執行的時間,並從新放到隊列中;今後處能夠看出固定速率和固定延遲的定時任務的區別
                            queue.rescheduleMin(
                              task.period<0 ? currentTime   - task.period
                                            : executionTime + task.period);
                        }
                    }
                }
                if (!taskFired) // 若是最小堆頂的定時任務還未到執行時間,則調用wait超時,直到超時或者被其它線程調用notify方法喚醒
                    queue.wait(executionTime - currentTime);
            }
            if (taskFired)  // Task fired; run it, holding no locks
                task.run();
        } catch(InterruptedException e) {
        }
    }
}

mainLoop方法是一個while死循環,不斷地從TaskQueue中獲取定時任務來進行執行,若是位於最小堆頂的定時任務過了計劃執行時間,就執行;不然就調用wait方法進行等待,直到超時,或者被其它線程調用notify方法喚醒。Timer能夠經過設置TimerTask的newTaskMayBeScheduled爲false來讓TimerTask優雅的退出。另外mainLoop方法的源代碼也能夠看出,之因此須要在建立TimerTask對象時將TimerQueue對象傳進來,是由於Timer和TimerTask之間經過TimerQueue進行同步;同時也能夠看出固定速率和固定延遲執行的定時任務之間的區別。

 

從上面的源碼學習中能夠看出,Timer是使用了最小堆來進行定時任務調度,原理比較簡單易懂。因爲Timer使用單線程來執行定時任務,所以不適合有大量定時任務,執行時間比較久的需求,一旦某個定時任務執行時間過久,就存在影響其它定時任務執行的可能。

相關文章
相關標籤/搜索