Timer-計時器


Timer 線程調度任務

本質上每個Timer對象都是一個單個后台線程Thread,用於依次執行該對象的所有任務。當Timer對象被new出來時,后台線程就會啟動,沒有任務會wait(),直到添加任務后被喚醒。
添加的任務應該是能很快完成的。如果某個任務執行時間過長(超過間隔時間period),因為Timer是單線程,它會導致后面任務的執行時間被延遲執行和快速執行。

假如我們創建了一個定時器對象Timer,設置執行間隔period=10s。
我們的理想狀態是,每個任務完成的時間小於10s,這樣每個任務執行的時間軸是0,10,20,30...
但有可能每個任務執行的時間有波動,導致第一次執行時間大於10s且結束后會立即執行第二次任務,也會影響后續的任務執行時間,達不到一個定時的效果(任務只會延后,不會提前執行)。

構造器

public Timer(String name, boolean isDaemon) {
    // 定時器名稱(線程名)
    thread.setName(name);
    // 是否設置定時器為守護線程,默認是false 
    thread.setDaemon(isDaemon);
    // 對象創建好立即執行
    thread.start();
}

說一下isDemon作用,如果我們在main線程中創建定時器(創建好是立即在后台執行的),這時候即使main線程結束了,定時器仍會在執行。如果setDemon(true),則定時器是一個后台執行的守護線程,main線程如果結束了,定時器也會立即結束。

成員參數

// 定時器任務隊列,與TimerThread共享
private final TaskQueue queue = new TaskQueue();

// Timer后台執行線程
private final TimerThread thread = new TimerThread(queue);

方法

// 一次性任務
public void schedule(TimerTask task, Date time);

// period傳遞給sched方法為負數
public void schedule(TimerTask task, long delay, long period);
public void schedule(TimerTask task, Date firstTime, long period);

// period傳遞給sched方法為正數
public void scheduleAtFixedRate(TimerTask task, long delay, long period);
public void scheduleAtFixedRate(TimerTask task, Date firstTime, long period);

// 終止定時器
public void cancel();

// 清理queue中cancelled狀態的任務,返回刪除的數量
public int purge()

TimerTask

能被Timer調度一次或多次的任務抽象類,實現了Runnable接口;另外同一個TimerTask能被不同Timer調度

成員參數

// 一個TimerTask對象能被多個Timer調度,修改該對象成員變量時需要使用synchronized進行同步
final Object lock = new Object();

/**
  實例對象自己維護了一個狀態state:
  VIRGIN :剛創建的TimerTask默認值(初始值)
  SCHEDULED :調用schedule方法放入queue前,設置peiod、nextExecutionTime、state
  EXECUTED :非重復任務已執行或正在執行中,還未被取消
  CANCELLED :任務已被取消(調用cancel)
*/
int state = VIRGIN;

// 下次執行時間,格式System.currentTimeMillis
// 對於重復任務,該字段在每次任務執行之前更新
long nextExecutionTime;

// 重復任務的執行間隔時間 毫秒ms
// 如果是正數,表示固定速度執行(每次執行時間在創建時就確定了,時間軸不變)
// 如果是負數,表示固定延遲執行(相對上次執行時間固定間隔,時間軸可能會變大)
long period = 0;

方法

// 實現定時任務的業務邏輯
public abstract void run();

// 取消任務,返回當前state==SCHEDULED,並將state設置為CANCELLED
public boolean cancel();

TaskQueue

Timer的內部優先隊列,是一個二叉堆,按nextExecutionTime排序的最小堆。

這邊講個小知識,優先隊列雖然本質是個完全二叉樹,但實現是通過數組。每次取queue[1],有人可能會問為什么不是取queue[0]呢?因為一棵深度為n的完全二叉樹節點總個數是2n-1,數組長度一般都是2n,所以數組多出一個位置queue[0]不存放數據。這樣還有個好處,如果一個二叉樹節點的數組位置是queue[i],那么它兩個左右子節點的可以分別表示成queue[2i],queue[2i+1]。

TimerThread

Timer內部線程對象,繼承於Thread

成員變量

// 
boolean newTasksMayBeScheduled = true;

// 
private TaskQueue queue;

方法

// 不廢話,直接干(TimerThread的run方法體)
private void mainLoop() {
    // Timer后台線程一直執行
    while (true) {
        try {
            TimerTask task;
            boolean taskFired;
            synchronized(queue) {
                // 任務隊列為空 線程進行等待
                while (queue.isEmpty() && newTasksMayBeScheduled)
                    queue.wait();
                // 等待結束后,隊列仍為空則終止定時器
                if (queue.isEmpty())
                    break; // Queue is empty and will forever remain; die

                // 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;  // No action required, poll queue again
                    }
                    currentTime = System.currentTimeMillis();
                    executionTime = task.nextExecutionTime;
                    // 判斷任務是否到達執行的時間
                    if (taskFired = (executionTime<=currentTime)) {
                        // 一次性任務,執行完丟棄
                        if (task.period == 0) { // Non-repeating, remove
                            queue.removeMin();
                            task.state = TimerTask.EXECUTED;
                        } else { // 重復性任務,需要
                            queue.rescheduleMin(
                                 // 設置該任務下次的執行時間nextExecutionTime
                                 // period<0 固定延遲:當前時間+period
                                 // period>0 固定速率:預期執行時間+period
                                 task.period<0 ? currentTime - task.period : executionTime + task.period);
                        }
                    }
                }
                if (!taskFired) // 任務還未到執行時間,繼續等待固定時長
                    queue.wait(executionTime - currentTime);
            }
            if (taskFired)  // Task fired; run it, holding no locks
                task.run(); // 執行任務
        } catch(InterruptedException e) {
        }
    }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM