Timer線程介紹
Timer 是一個用來將將要執行的任務放在後臺線程執行的工具。被執行性的任務可以執行一次就結束了,也可以通過設定的時間間隔來週期性的執行。
Timer的基本使用
創建一個 Timer 對象,調用其 schedule() 方法傳遞一個TimerTask ,同時設定多長時間後開始執行及執行週期。這樣,就可以執行任務了。代碼如下:
public void test() {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
while (true) {
System.err.println(Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}, 10000);
}
Timer 的源碼分析
Timer使用涉及的三個相關類
- TimerThread 真正執行任務的線程,Thread 的子類
- TimerTask 被執行的任務,可以看做一個Runnable 對象
- TaskQueue 存放 TimerTask 的類,封裝了一個 TimerTask 數組用來保存待執行的任務
創建Timer對象
Timer timer = new Timer();
分析 Timer 的源碼,可以看到,Timer 對象分裝了 TaskQueue對象和 TimerThread對象
public class Timer {
private final TaskQueue queue = new TaskQueue();
private final TimerThread thread = new TimerThread(queue);
//省略其他代碼
}
在通過構造方法創建對象時,最中會調用如下構造方法,可以看出,對象創建過程中就開啓了線程。線程的 start() 方法最終會調用其 run() 方法。
public Timer(String name, boolean isDaemon) {
thread.setName(name);
thread.setDaemon(isDaemon);
thread.start();
}
由於 TimerThread 繼承了 Thread ,同時重寫了父類的 run() 方法,所以最終會執行 TimerThread 的 run() 方法。
class TimerThread{
private TaskQueue queue;
TimerThread(TaskQueue queue) {
this.queue = queue;
}
public void run() {
try {
mainLoop();
} finally {
// Someone killed this Thread, behave as if Timer cancelled
synchronized(queue) {
newTasksMayBeScheduled = false;
queue.clear(); // Eliminate obsolete references
}
}
}
}
run() 方法會調用 mainLoop() 方法,這纔是任務的核心代碼。
/**
* The main timer loop. (See class comment.)
*/
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)) {
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) {
}
}
}
我們可以看到,mainLoop() 方法裏面有個死循環,同時對queue對象進行同步加鎖 synchronized ,先判斷任務列表 queue 是否爲空,如果爲空,則調用 queue.wait() 使當線程進行休眠狀態。所以說,如果不向 queue 中添加任務,那麼該線程將永遠處於休眠狀態。那麼如何添加任務呢? timer.schedule() 方法的作用就出來了。
添加任務
timer.schedule(TimerTask,long) ,最終會調用 sched() 方法。
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();
}
}
添加任務過程中先進行各種條件的判斷,同樣也對 queue 對象進行了加鎖,調用 queue.add(task) 將任務添加到任務列表中。那麼任務添加了,如何執行呢? queue.notify() 會喚醒其他享有該對象的鎖的的線程繼續執行任務。我們可以看到另一個對該對象加鎖的線程正是TimerThread ,而此時 TimerThread 又調用了 wait() 方法處於等待狀態,此時 notify() 方法會通知它繼續執行任務。
執行任務
再次回到TimerThread的mainLoop() 方法中,當線程被喚醒後,繼續執行字後的代碼。同時進行條件判斷,如果被喚醒後線程列表還是爲空,那麼線程到此結束。
從線程列表中取出線程,查看其執行時間及是否需要週期性的執行。進行一系列操作後,開始執行任務,調用 task.run() 方法,這樣,我們創建的 TimerTask 對象的 run() 方法就會執行了。
當我們不需要執行任務後,需要調用 timer 的 cancel() 方法取消任務執行。
public void cancel() {
synchronized(queue) {
thread.newTasksMayBeScheduled = false;
queue.clear();
queue.notify(); // In case queue was already empty.
}
}
使用注意
- 通常我們在Android中會週期性的執行一些任務,當這些任務有和界面進行交互的功能時,我們退出界面前一定要先取消任務,不然會引發異常,導致程序掛掉。
- 當項目中存在大量的定期任務時,可以考慮使用Quartz