java定時器之Timer使用與原理分析
Timer和TimerTask
Timer是jdk中提供的一個定時器工具,使用的時候會在主執行緒之外起一個單獨的執行緒執行指定的計劃任務,可以指定執行一次或者反覆執行多次。
TimerTask是一個實現了Runnable介面的抽象類,代表一個可以被Timer執行的任務。
【使用舉例】
【schedule(TimerTask task, long delay) 延遲 delay 毫秒 執行】
public static void main(String[] args) { for (int i = 0; i < 10; ++i) { new Timer("timer - " + i).schedule(new TimerTask() { @Override public void run() { println(Thread.currentThread().getName() + " run "); } }, 1000); } }
【schedule(TimerTask task, Date time) 特定時間執行】
public static void main(String[] args) { for (int i = 0; i < 10; ++i) { new Timer("timer - " + i).schedule(new TimerTask() { @Override public void run() { println(Thread.currentThread().getName() + " run "); } }, new Date(System.currentTimeMillis() + 2000)); } }
【schedule(TimerTask task, long delay, long period) 延遲 delay 執行並每隔period 執行一次】
public static void main(String[] args) { for (int i = 0; i < 10; ++i) { new Timer("timer - " + i).schedule(new TimerTask() { @Override public void run() { println(Thread.currentThread().getName() + " run "); } }, 2000 , 3000); } }
【原理分析】
timer底層是把一個個任務放在一個TaskQueue中,TaskQueue是以平衡二進位制堆表示的優先順序佇列,他是通過nextExecutionTime進行優先順序排序的,距離下次執行時間越短優先順序越高,通過getMin()獲得queue[1]
並且出隊的時候通過synchronized保證執行緒安全,延遲執行和特定時間執行的底層實現類似,原始碼如下:
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();
}
}
我們主要來看下週期性排程通過什麼方式實現的,我們直接來分析原始碼如下:
private void mainLoop() {
// 首先一直監聽佇列中有沒有任務
while (true) {
try {
TimerTask task;
boolean taskFired;
// 同步,保證任務執行順序
synchronized(queue) {
// Wait for queue to become non-empty
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)) {
// 到這裡是延遲執行和特定時間點執行已經結束了,狀態標記為EXECUTED,週期性執行繼續往下走
if (task.period == 0) { // Non-repeating, remove
queue.removeMin();
task.state = TimerTask.EXECUTED;
} else { // Repeating task, reschedule
// 這裡他又重新計算了下下個任務的執行,並且任務還在佇列中
queue.rescheduleMin(
task.period<0 ? currentTime - task.period
: executionTime + task.period);
}
}
}
// 如果任務執行時間大於當前時間說明任務還沒點,繼續等,否則執行run程式碼塊
if (!taskFired) // Task hasn't yet fired; wait
queue.wait(executionTime - currentTime);
}
if (taskFired) // Task fired; run it, holding no locks
task.run();
} catch(InterruptedException e) {
}
}
}
}
這種定時器適合單點或者多臺同時執行互不影響的場景
【缺點】
1、首先Timer對排程的支援是基於絕對時間的,而不是相對時間,所以它對系統時間的改變非常敏感。
2、其次Timer執行緒是不會捕獲異常的,如果TimerTask丟擲的了未檢查異常則會導致Timer執行緒終止,同時Timer也不會重新恢復執行緒的執行,他會錯誤的認為整個Timer執行緒都會取消。同時,已經被安排單尚未執行的TimerTask也不會再執行了,新的任務也不能被排程。故如果TimerTask丟擲未檢查的異常,Timer將會產生無法預料的行為
3、Timer在執行定時任務時只會建立一個執行緒任務,如果存在多個執行緒,若其中某個執行緒因為某種原因而導致執行緒任務執行時間過長,超過了兩個任務的間隔時間,會導致下一個任務執行時間滯後
這些缺點可以通過ScheduledExecutorService來代替