如何使用
首先定義一個類,繼承TimerTask
static class MyTimerTask extends TimerTask {
private final MyWebSocketClient client;
public MyTimerTask(MyWebSocketClient client) {
this.client = client;
}
@Override
public void run() {
System.out.print("任務執行");
}
}
配置MyTimerTask的執行政策
Timer timer = new Timer();
MyTimerTask timerTask = new MyTimerTask(client);
timer.schedule(timerTask, 1000, 2000);
上訴執行政策的意思為:從現在起過1000毫秒以後,每隔2000毫秒執行一次。
除了支援這種模式的政策設定還有很多
// time為Date類型:在指定時間執行一次。
timer.schedule(task, time);
// firstTime為Date類型,period為long
// 從firstTime時刻開始,每隔period毫秒執行一次。
timer.schedule(task, firstTime, period);
// delay 為long類型:從現在起過delay毫秒執行一次
timer.schedule(task, delay)
// delay為long,period為long:從現在起過delay毫秒以後,每隔period
// 毫秒執行一次。
timer.schedule(task, delay, period)
原理分析
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在執行定時任務時隻會建立一個線程任務,如果存在多個線程,若其中某個線程因為某種原因而導緻線程任務執行時間過長,超過了兩個任務的間隔時間,會導緻下一個任務執行時間滞後