Timer和TimerTask源碼解讀
Timer是一種定時器工具,用來在一個背景線程計劃執行指定任務。它可以計劃執行一個任務一次或反複多次。
TimerTask是一個實作了Runnable接口的抽象類,代表一個可以被Timer執行的任務。
Timer和TimerTask基本使用
使用Timer線程實作和計劃執行一個任務:
- 實作自定義的TimerTask的子類,run方法包含要執行的任務代碼。
- 執行個體化Timer類,建立計時器背景線程。
- 執行個體化任務對象 (new RemindTask()).
- 制定執行計劃。這下面案例中采用schedule方法,第一個參數是TimerTask對象,第二個參數表示開始執行前的延時時間(機關是milliseconds)。還有一種方法可以指定任務的執行時間,例如指定任務在11:01 p.m.執行:
案例:
public class Reminder {
Timer timer;
public Reminder(int seconds) {
timer = new Timer();
timer.schedule(new RemindTask(), seconds * 1000);
}
class RemindTask extends TimerTask {
public void run() {
System.out.println("Time's up!");
timer.cancel();
}
}
public static void main(String args[]) {
System.out.println("About to schedule task.");
new Reminder(5);
System.out.println("Task scheduled.");
}
}
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.HOUR_OF_DAY, 23);
calendar.set(Calendar.MINUTE, 1);
calendar.set(Calendar.SECOND, 0);
Date time = calendar.getTime();
timer = new Timer();
timer.schedule(new RemindTask(), time);
終止Timer線程
預設情況下,隻要一個程式的timer線程在運作,那麼這個程式就會保持運作。可以通過以下四種方法終止一個timer線程:
- 調用timer的cancle方法。你可以從程式的任何地方調用此方法,甚至在一個timer task的run方法裡。
- 讓timer線程成為一個daemon線程(可以在建立timer時使用new Timer(true)達到這個目地),這樣當程式隻有daemon線程的時候,它就會自動終止運作。
- 當timer相關的所有task執行完畢以後,删除所有此timer對象的引用(置成null),這樣timer線程也會終止。
- 調用System.exit方法,使整個程式(所有線程)終止。
TimerTask任務:
TimerTask的狀态有4種:
- VIRGIN:未使用的,即初始狀态
- SCHEDULED:任務被放到TaskQueue,但是還未執行
- EXECUTED:隻針對隻執行一次的TimerTask,表示已經被執行,或者正準備執行
- CANCELLED:被取消
public abstract class TimerTask implements Runnable {
final Object lock = new Object();//鎖
int state = VIRGIN;
static final int VIRGIN = 0;//VIRGIN:未使用的,即初始狀态
static final int SCHEDULED = 1;//任務被放到TaskQueue,但是還未執行
static final int EXECUTED = 2;//隻針對隻執行一次的TimerTask,表示已經被執行,或者正準備執行
static final int CANCELLED = 3;//被取消
long nextExecutionTime;//執行時間屬性
long period = 0;//間隔
protected TimerTask() {
}
public abstract void run();
public boolean cancel() {
synchronized(lock) {
boolean result = (state == SCHEDULED);
state = CANCELLED;
return result;
}
}
public long scheduledExecutionTime() {
synchronized(lock) {
return (period < 0 ? nextExecutionTime + period
: nextExecutionTime - period);
}
}
}
Timer定時器:
Timer定時器的實作原理,是通過Object.wait(timeout),來進行的線程阻塞,timeout是根據下次執行實際和目前實際之差來計算。實際上,可以歸結為一個多線程協作問題。
TaskQueue:
TaskQueue中存放一些列将要執行的TimerTask,以數組的形式存放,下标約小(注:下标為0不處理,即使用的最小下标為1),則表明優先級越高。
private final TaskQueue queue = new TaskQueue();
class TaskQueue {
//面有一個 TimerTask 的數組,注釋中指出這個隊列是一個優先級隊列(平衡二叉堆)
//按照執行時間進行判斷,queue[0]是執行時間最早的任務,反之最晚
private TimerTask[] queue = new TimerTask[128];
private int size = 0;
int size() {}
//加入任務的方法
void add(TimerTask task) {
// Grow backing store if necessary
if (size + 1 == queue.length)
queue = Arrays.copyOf(queue, 2*queue.length);//如果queue滿容就擴大兩倍
queue[++size] = task;
//根據二叉堆特性調整
fixUp(size);
}
TimerTask getMin() {return queue[1];}
TimerTask get(int i) {return queue[i];}
//二叉堆删堆頂元素,堆頂放到堆末尾,size--
void removeMin() {
queue[1] = queue[size];
queue[size--] = null; // Drop extra reference to prevent memory leak
fixDown(1);}
void quickRemove(int i) {
assert i <= size;
queue[i] = queue[size];
queue[size--] = null; // Drop extra ref to prevent memory leak
}
void rescheduleMin(long newTime) {queue[1].nextExecutionTime = newTime;fixDown(1);}
boolean isEmpty() {...}
void clear() {...}
private void fixUp(int k) {...}
private void fixDown(int k) {... }
void heapify() {...}
}
TimerThread:
TimerThread為Thread的擴充類,會一直從TaskQueue中擷取下标為1的TimerTask進行執行。并根據該TimerTask是否需要重複執行來決定是否放回到TaskQueue中。
private final TimerThread thread = new TimerThread(queue);
class TimerThread extends Thread {
//還有沒有指向 Timer 對象的活躍引用,如果标記為false,且任務隊列裡沒有任務了,就說明可以停止 Timer 的運作
boolean newTasksMayBeScheduled = true;
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
}
}
}
private void mainLoop() {
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
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) {
}
}
}
}
schedule()方法:
// delay 是采用系統時間加上delay的毫秒數來進行的
public void schedule(TimerTask task, Date firstTime, long period) {
if (period <= 0)
throw new IllegalArgumentException("Non-positive period.");
sched(task, firstTime.getTime(), -period);
}
以固定速率執行,在delay之後開始
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);
}
public void schedule(TimerTask task, Date time) {
sched(task, time.getTime(), 0);
}
public void schedule(TimerTask task, long delay) {
if (delay < 0)
throw new IllegalArgumentException("Negative delay.");
sched(task, System.currentTimeMillis()+delay, 0);
}
Timer和TimerTask雙重定時器:
使用定時器,間隔 4 秒執行一次,再間隔 2 秒執行一次
public class DoubleTimer extends TimerTask {
int sum = 0;
public static void main(String[] args) {
Timer timer = new Timer();//設定一個定時器
timer.schedule(new DoubleTimer(),2000);//2秒後執行TimerTest中的run方法
while (true) {
System.out.println(new Date().getSeconds());
try {
Thread.sleep(1000);//間隔一秒
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
@Override
public void run() {
sum = (sum +1)%2;//結果01交替
System.err.println("執行定時任務,隔兩秒四秒交替列印");
new Timer().schedule(new DoubleTimer(), 2000+2000*sum);
}
}