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) {
}
}
}