這是我參與新手入門的第1篇文章。java
public static void main(String[] args) {
TimerTask timerTask = new TimerTask() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(2000);
} catch (Exception e) {
// TODO: handle exception
}
System.out.println("TimerTask當前時間:"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
}
}
};
Timer timer = new Timer();
long delay = 0;
long period = 1000;
timer.schedule(timerTask, delay, period);
}
複製代碼
效果:web
TimerTask的原理是什麼那?api
實際上經過上方的示例代碼你就能發現,TimerTask就是一個實現了run方法的類,TimerTask是一個抽象類,實現了Runnable,提供了抽象方法run()。數組
public abstract class TimerTask implements Runnable {
//task的狀態,默認是VIRGIN,共有4個狀態
int state = VIRGIN;
//未執行
static final int VIRGIN = 0;
//非重複任務,未被執行
static final int SCHEDULED = 1;
//非重複任務已執行或正執行,而且沒有被取消
static final int EXECUTED = 2;
//已經取消
static final int CANCELLED = 3;
//下次執行時間
long nextExecutionTime;
//任務執行的週期
long period = 0;
/** * The action to be performed by this timer task. */
public abstract void run();
//這個就是把當前這個task設置爲取消狀態,Timer也有一個cancel方法,會面會提到
public boolean cancel() {
synchronized(lock) {
boolean result = (state == SCHEDULED);
state = CANCELLED;
return result;
}
}
//還有一個scheduledExecutionTime方法 就不提了
}
複製代碼
關鍵在於 Timer
💥markdown
public Timer(String name) {
thread.setName(name);
thread.start();
}
複製代碼
這裏的thread就是一個線程,數據結構
/** * The timer thread. */
private final TimerThread thread = new TimerThread(queue);
複製代碼
而這個TimerThread就被定義在Timer類中,從註釋中能夠看出,這個線程就是用來執行定時任務的具體線程
,當隊列中的任務被觸發時執行它們。app
我會在1.3節中講這個TimerThread
和隊列TaskQueue
。😎ide
/** * This "helper class" implements the timer's task execution thread, which * waits for tasks on the timer queue, executions them when they fire, * reschedules repeating tasks, and removes cancelled tasks and spent * non-repeating tasks from the queue. */
class TimerThread extends Thread {
}
複製代碼
此時,回過頭來再看其餘構造函數就一目瞭然了,第一個無參構造函數經過Timer爲前綴名構造一個線程,裏面的this就是上面這個構造函數,這裏的serialNumber()
就是去生成一個名字。函數
public Timer() {
this("Timer-" + serialNumber());
}
/** * This ID is used to generate thread names. */
private final static AtomicInteger nextSerialNumber = new AtomicInteger(0);
private static int serialNumber() {
return nextSerialNumber.getAndIncrement();
}
複製代碼
若是有不明白AtomicInteger()
的朋友能夠看着這篇博客: blog.csdn.net/fanrenxiang…oop
在代碼裏打個斷點就能夠清楚的看到這個線程的名字:
第二個構造函數傳入了是否爲後臺線程,若是是,主線程結束後會自動結束,不須要調用cancel。
public Timer(boolean isDaemon) {
this("Timer-" + serialNumber(), isDaemon);
}
複製代碼
爲何要設置爲後臺線程
,很經典的一個例子是在web應用中使用線程,當你把web應用關閉後,這個線程還在運行!🐄🍺
這是由於線程是JVM級別的,web應用關閉後,這個線程並無銷燬。具體能夠看這篇博客:blog.csdn.net/chetianyao8…
第四個構造函數能夠設置名字及是否爲後臺線程,而且啓動。
public Timer(String name, boolean isDaemon) {
thread.setName(name);
thread.setDaemon(isDaemon);
thread.start();
}
複製代碼
好,下面開始講Timer的方法,屬性放到1.3節中
timer提供了6個調度方法,其實都大同小異,先了解一下,看完1.3節再回過來看
public void schedule(TimerTask task, long delay) {
if (delay < 0)
throw new IllegalArgumentException("Negative delay.");
sched(task, System.currentTimeMillis()+delay, 0);
}
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 scheduleAtFixedRate(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);
}
複製代碼
能夠發現,最終都調用了sched
方法,而schedule
和scheduleAtFixedRate
的區別就是傳入的最終period的正負數,爲何會有這種區別?看完後面就懂了。
sched方法就是對task作一些設置,加到隊列裏,並作一次notify操做
。這些後面都會提到。
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; //task狀態
}
//添加到隊列
queue.add(task);
if (queue.getMin() == task)
queue.notify();
}
}
複製代碼
cancel()
方法,一旦執行,Timer就停掉了,爲何要把newTasksMayBeScheduled設置爲false
以及爲什調用notify(),也須要看後面的講解。
public void cancel() {
synchronized(queue) {
thread.newTasksMayBeScheduled = false;
queue.clear();
queue.notify(); // In case queue was already empty.
}
}
複製代碼
purge()
方法,當對TimerTask作了屢次cancel以後,隊列就混亂了,這時候就須要調用這個方法,回收空間並從新排列。
注意,這個cancel不是上面講Timer的cancel方法,是TimerTask的cancel方法
。
public int purge() {
int re0sult = 0;
synchronized(queue) {
for (int i = queue.size(); i > 0; i--) {
if (queue.get(i).state == TimerTask.CANCELLED) {
queue.quickRemove(i);
result++;
}
}
if (result != 0)
queue.heapify();
}
return result;
}
複製代碼
在上面已經提到過了Timer的兩個屬性thread和nextSerialNumber,此外還有一個queue,這個queue存儲的就是要調度的任務,也就是TimerTask。
TaskQueue的結構很簡單,一個數組,一個size。須要注意的一點是此隊列存儲範圍是queue[1]-queue[size]
。隊列默認大小是128,但,是支持擴容的。
private final TaskQueue queue = new TaskQueue();
class TaskQueue {
private TimerTask[] queue = new TimerTask[128];
private int size = 0;
}
複製代碼
擴容的方式是在增長TimerTask時進行操做
void add(TimerTask task) {
// 擴容在這
if (size + 1 == queue.length)
queue = Arrays.copyOf(queue, 2*queue.length);
queue[++size] = task;
fixUp(size);
}
複製代碼
TaskQueue提供了一系列的操做來對隊列進行處理,尤爲是排序。這裏面涉及很多數據結構的操做,大學數據結構不及格的能夠學一波。
TaskQueue原理上是一個平衡二叉堆
根節點的nextExecutionTime最小,queue[n]的子節點是queue[2n]和queue[2n+1],這些亂七八糟的就不說了,課本上都有。
size()、get(i)和add(TimerTask)這三個方法就不說了,getMin()是返回最近須要執行的任務,返回的就是queue[1]。
removeMin()
是刪除當前最近執行的任務,而刪除的操做很經典,大學數據結構必學的點,就是把隊尾賦給對頭,隊尾置爲null,而且從新排序。
void removeMin() {
queue[1] = queue[size];
queue[size--] = null; // Drop extra reference to prevent memory leak
fixDown(1);
}
複製代碼
fixDown(int)
的做用就是下濾,不斷的把queue[k]和它的子節點進行比較,直到它的nextExecutionTime小於等於子節點。
private void fixDown(int k) {
int j;
while ((j = k << 1) <= size && j > 0) {
if (j < size &&
queue[j].nextExecutionTime > queue[j+1].nextExecutionTime)
j++; // j indexes smallest kid
if (queue[k].nextExecutionTime <= queue[j].nextExecutionTime)
break;
TimerTask tmp = queue[j]; queue[j] = queue[k]; queue[k] = tmp;
k = j;
}
}
複製代碼
而fixUp(int)
的做用是上溢,不斷向上提高,在新增TimerTask時使用。
private void fixUp(int k) {
while (k > 1) {
int j = k >> 1;
if (queue[j].nextExecutionTime <= queue[k].nextExecutionTime)
break;
TimerTask tmp = queue[j]; queue[j] = queue[k]; queue[k] = tmp;
k = j;
}
}
複製代碼
quickRemove(int)
刪除指定的元素。爲何叫quick那,它只賦值沒有排序,排序要搭配heapify()。
void quickRemove(int i) {
assert i <= size;
queue[i] = queue[size];
queue[size--] = null; // Drop extra ref to prevent memory leak
}
void heapify() {
//對queue的後半段作排序
for (int i = size/2; i >= 1; i--)
fixDown(i);
}
複製代碼
rescheduleMin(newTime)
從新設置當前任務的下一次執行時間並排序,爲何取queue[1]?
由於在真正執行的時候,是每次取下次執行時間最短的,也就是queue[1]。
void rescheduleMin(long newTime) {
queue[1].nextExecutionTime = newTime;
fixDown(1);
}
複製代碼
在1.1節簡單的提了下TimerThread,這裏重點介紹下,由於它是Timer的重點👴👴👴
TimerThread很簡單,TimerThread就是具體去執行的地方。
先來看下TimerThread的源碼,主體就是 mainLoop()
方法。
class TimerThread extends Thread {
//標誌位,用於在mainLoop中判斷狀態
boolean newTasksMayBeScheduled = true;
private TaskQueue queue;
TimerThread(TaskQueue queue) {
this.queue = queue;
}
public void run() {
try {
//mainLoop的具體實現,下面會講
mainLoop();
} finally {
// 將參數置爲false,而且隊列清空
synchronized(queue) {
newTasksMayBeScheduled = false;
queue.clear(); // Eliminate obsolete references
}
}
}
}
複製代碼
那麼TimerThread是如何運行的?我來給你捋一下
//一開始咱們是new了一個Timer(),經過Timer的無參構造函數配置了默認的線程名字並執行了thread.start()
//而且經過Timer的schedule()方法把TimerTask放到隊列裏並設置了延遲、週期和狀態。
Timer timer = new Timer();
timer.schedule(timerTask, delay, period);
public Timer() {
this("Timer-" + serialNumber());
}
public Timer(String name) {
//這裏的thread就是被final修飾的TimerThread,new了一個TimerThread並傳遞了隊列queue
//private final TimerThread thread = new TimerThread(queue);
thread.setName(name);
thread.start();
}
//而TimerThread是有一個TaskQueue的屬性和重載的構造函數,這個重載的構造函數接收了queue
private TaskQueue queue;
TimerThread(TaskQueue queue) {
this.queue = queue;
}
//一旦執行thread.start(),TimerThread的run方法就會執行,具體執行就在mainLoop()方法中
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) {
}
}
}
//大致讀一下這個方法,你就會發現除非遇到break或者遇到不能捕獲的異常,它就是個死循環。
while (queue.isEmpty() && newTasksMayBeScheduled)
queue.wait();
//跳出循環的條件就是queue不爲空或者newTasksMayBeScheduled爲false
//那麼wait就是等到其餘地方對queue發生nitify操做,其實就是在調用cancel的時候
//這時標誌位是false,會跳出循環,而且對queue設置了clear操做, 直接就跳出了外部的死循環。
public void cancel() {
synchronized(queue) {
thread.newTasksMayBeScheduled = false;
queue.clear();
queue.notify(); // In case queue was already empty.
}
}
//還有一處,是Timer的屬性threadReaper調用finalize的時候
//這個threadReaper只重寫了finalize方法,GC的時候調用
private final Object threadReaper = new Object() {
protected void finalize() throws Throwable {
synchronized(queue) {
thread.newTasksMayBeScheduled = false;
queue.notify(); // In case queue is empty.
}
}
};
//還有就是當對queue執行add操做的時候,在Timer中的sched方法,前邊有提到。此時queue不爲空,就跳出了循環。
//以後是判斷該task是否被取消
if (task.state == TimerTask.CANCELLED) {
queue.removeMin();
continue; // No action required, poll queue again
}
//再以後是取當前的系統時間和上次預計的執行時間,若是當前系統時間已經超了,就趕忙執行。
//不過在執行以前須要判斷是不是重複任務。
//判斷一下period是否爲0,0就表明一次性任務,刪掉。若是不是,就調用rescheduleMin設置下一次執行時間並排序。
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);
}
}
//這裏頗有意思,若是period是負數,下次執行時間就是當前系統時間+週期時間。若是是正數就是原計算的下次執行時間+週期時間。
//這就是schedule和scheduleAtFixedRate的區別。換一下參數的正負數,就和另外一個方法同樣。。
//再日後就是若是當前這個task執行時間還沒到就等待一段時間,
if (!taskFired) // Task hasn't yet fired; wait
queue.wait(executionTime - currentTime);
//若是時間到了,就執行了
if (taskFired) // Task fired; run it, holding no locks
task.run();
複製代碼
好,至此整個執行流程就結束了,是否是很簡單!😏😏😏
在回過頭去看1.2節,是否是清晰又明瞭!👻
Timer能夠分四個部分:
須要注意的點是,若是要用TimerTask,必定要記得使用try catch,若是遇到不能捕獲的異常Timer就終止了。
這裏能夠參考一個生產過程當中遇到的問題:blog.verysu.com/article/435