在平時的開發中,確定須要使用定時任務,而 Java 1.3 版本提供了一個 java.util.Timer 定時任務類。今天一塊兒來看看這個類。java
Timer 相關的有 3 個類:程序員
Timer :面向程序員的API 都在這個類中。 TaskQuue: 存儲任務。 TimerThread: 執行任務的線程。數組
這個類的構造方法有 4 個:oop
Timer() 建立一個新計時器。
Timer(boolean isDaemon) 建立一個新計時器,能夠指定其相關的線程做爲守護程序運行。
Timer(String name) 建立一個新計時器,其相關的線程具備指定的名稱。
Timer(String name, boolean isDaemon) 建立一個新計時器,其相關的線程具備指定的名稱,而且能夠指定做爲守護程序運行。
複製代碼
程序員能夠使用的 API 以下:ui
void cancel() 終止此計時器,丟棄全部當前已安排的任務。 int purge() 今後計時器的任務隊列中移除全部已取消的任務。 void schedule(TimerTask task, Date time) 安排在指定的時間執行指定的任務。 void schedule(TimerTask task, Date firstTime, long period) 安排指定的任務在指定的時間開始進行重複的固定延遲執行。 void schedule(TimerTask task, long delay) 安排在指定延遲後執行指定的任務。 void schedule(TimerTask task, long delay, long period) 安排指定的任務從指定的延遲後開始進行重複的固定延遲執行。 void scheduleAtFixedRate(TimerTask task, Date firstTime, long period) 安排指定的任務在指定的時間開始進行重複的固定速率執行。 void scheduleAtFixedRate(TimerTask task, long delay, long period) 安排指定的任務在指定的延遲後開始進行重複的固定速率執行。 複製代碼
下面從幾個具備表明性的方法開始分析 Timer 的源碼。this
Timer timer = new Timer();
public Timer() {
this("Timer-" + serialNumber());
}
public Timer(String name) {
thread.setName(name);
thread.start();
}
/** * The timer thread. */
private final TimerThread thread = new TimerThread(queue);
private final TaskQueue queue = new TaskQueue();
private TimerTask[] queue = new TimerTask[128];
複製代碼
從上面一連串的構造方法中,能夠看出,Timer 內部使用了一個線程 TimerThread,線程的構造參數是一個隊列(數組)。spa
而後直接啓動了這個線程,默認是非守護模式的。線程
而這個線程的 run 方法又是如何的呢?code
public void run() {
try {
mainLoop();
} finally {
// Someone killed this Thread, behave as if Timer cancelled
synchronized(queue) {
newTasksMayBeScheduled = false;
queue.clear(); // Eliminate obsolete references
}
}
}
複製代碼
主要執行 mainLoop 方法,當任務結束後,清除隊列。並不在接受新的任務。對象
那麼這個 mainLoop 方法的邏輯是什麼呢?猜測一下,確定是執行隊列中的任務。
private void mainLoop() {
while (true) {
try {
TimerTask task;
boolean taskFired;
synchronized(queue) {
// 若是隊列是空 且 newTasksMayBeScheduled 是 true,阻塞等待
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 { // Repeating task, reschedule
// 若是任務是重複的。從新調度任務時間,以便下次執行。
queue.rescheduleMin(
task.period<0 ? currentTime - task.period
: executionTime + task.period);
}
}
}
// 若是時間沒到,就等代指定時間
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) {
// 若是有中斷異常就忽略。
}
}
}
複製代碼
一如既往,寫了不少註釋,簡單說說邏輯:
這裏有幾個注意的地方:
So,必定不要在本身的任務裏拋出異常,不然必定會影響整個定時任務。
timer.schedule(new MyTask(), 1000, 2000);
複製代碼
以上定義了一個任務,1 秒後執行,重複執行時間 2 秒。
schedule 代碼以下:
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.");
sched(task, System.currentTimeMillis()+delay, -period);
}
複製代碼
在 dealy 時間的基礎上,加上了當前時間,將 period 變成負數。
看看 sched 方法實現:
private void sched(TimerTask task, long time, long period) {
if (time < 0)
throw new IllegalArgumentException("Illegal execution time.");
// 防止數值溢出
if (Math.abs(period) > (Long.MAX_VALUE >> 1))
period >>= 1;
synchronized(queue) {
// 若是該變量是 false ,說明任務線程中止了,拋出異常
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();
}
}
複製代碼
總結一下該方法,將任務添加進隊列,若是調度線程結束了,就拋出異常—— 不能再添加。若是添加成功以後,獲取到的第一個任務就是這個任務,說明調度線程阻塞了,那就喚醒他。
從一個定時任務的角度講,Timer 很是的簡單,使用一個線程,使用一個隊列。在簡單的場合,Timer 確實可以知足需求,但 Timer 仍是有不少的缺陷:
不能 catch 住非線程中斷異常,若是用戶任務異常,將會致使整個 Timer 中止。
默認狀況下不是守護線程,也就是說,他會阻止應用程序中止。你能夠使用 cancel 方法中止他。
若是 Timer 由於 stop 方法獲取用戶任務異常終止了,那麼將不再能向隊列中添加任務了。不然拋出異常。
若是某個任務的執行時間太長,那麼他將會 「獨佔」 計時器的任務執行現場。致使延遲後續任務的執行,而且會將任務 「堆」 在一塊兒。
So, 大規模的生產環境中,不建議使用 Timer,而是使用 JUC 的 ScheduledThreadPoolExecutor。樓主將在後面的文章中分析 ScheduledThreadPoolExecutor 的實現,相比較 Timer 有什麼好處。