java定时器-Timer和TimerTask详解
1、例子入手
package pers.growing.test;import java.util.Timer; import java.util.TimerTask;public class Main {/*** 延遲100ms后,間隔1s打印出:hello world** @param args* @throws InterruptedException*/public static void main(String[] args) throws InterruptedException {Timer t = new Timer();t.scheduleAtFixedRate(new TimerTask() {@Overridepublic void run() {System.out.println("hello world");}}, 100, 1000);}}結果:
hello world hello world hello world hello world hello world hello world hello world2、類講解
TimerTask.java:主要為任務的具體內容。
Timer.java中含有3個類:Timer、TimerThread、TaskQueue。
三者關系為:TaskQueue中存放一些列將要執行的TimerTask,以數組的形式存放,下標約小(注:下標為0不處理,即使用的最小下標為1),則表明優先級越高。
TimerThread為Thread的擴展類,會一直從TaskQueue中獲取下標為1的TimerTask進行執行。并根據該TimerTask是否需要重復執行來決定是否放回到TaskQueue中。
Timer用于配置用戶期望的任務執行時間、執行次數、執行內容。它內部會配置TimerThread、TaskQueue。
3、源碼解讀
Timer類下的方法如下:
Timer中涉及到4個成員變量,queue、thread已經在上面介紹過了,對于nextSerialNumber,只是用于命名默認的thread名稱使用。threadReaper為了在GC時進行相關處理,后面再介紹。
Timer的構造函數實現大同小異,以Timer(String,boolean)為例:
public Timer(String name, boolean isDaemon) {thread.setName(name); //設置成員變量的線程名稱thread.setDaemon(isDaemon); //該線程是否為守護線程thread.start();//起線程}schedule()以schedule(TimerTask,long,long)為例:
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); //最后調用了sched方法}這一塊有困惑的可能是為什么period取了負數?而且所有的schedule(...)方法,最后傳給sched中的period都是負的。之后再介紹。
scheduleAtFixedRate()以scheduleAtFixedRate(TimerTask,long,long)為例:
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似乎區別不大,唯一有區別的是schedule最后傳值給sched的period是負數,而scheduleAtFixedRate傳的是正數。
在schedule方法的注釋上,有這段內容:
* <p>In fixed-delay execution, each execution is scheduled relative to* the actual execution time of the previous execution. If an execution* is delayed for any reason (such as garbage collection or other* background activity), subsequent executions will be delayed as well.* In the long run, the frequency of execution will generally be slightly* lower than the reciprocal of the specified period (assuming the system* clock underlying <tt>Object.wait(long)</tt> is accurate).翻譯:如果出現某一次任務的延遲,那么之后的任務都會以period為周期進行延遲。
而scheduleAtFixedRate方法也有對應注釋:
* <p>In fixed-rate execution, each execution is scheduled relative to the* scheduled execution time of the initial execution. If an execution is* delayed for any reason (such as garbage collection or other background* activity), two or more executions will occur in rapid succession to* "catch up." In the long run, the frequency of execution will be* exactly the reciprocal of the specified period (assuming the system* clock underlying <tt>Object.wait(long)</tt> is accurate).翻譯:每次的執行都是以初始時計算好的時間為準,如果出現某次任務的延遲,則之后的任務會快速執行,即按計劃時間執行。
那么看下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; //給TimerTask賦值task.period = period;task.state = TimerTask.SCHEDULED;}queue.add(task);//將TimerTask放到隊列中,并進行隊列排序if (queue.getMin() == task)//如果隊列里恰好下標為1的任務為當前的task,則直接喚醒queue.notify();}}queue中的add和getMin操作如下:
void add(TimerTask task) {// Grow backing store if necessaryif (size + 1 == queue.length)queue = Arrays.copyOf(queue, 2*queue.length);queue[++size] = task;fixUp(size);//讓task進行排序,排序并不是十分嚴謹,將nextExecutionTime較小的往前排}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;}}TimerTask getMin() { //注意最小的值是從下標為1的獲取,queue[0]其實沒用到return queue[1];}數據已經放到queue中了,那么看下是什么時候執行的。在之前Timer的構造函數這塊,有一句是:thread.start();說明TimerThread在Timer初始化之后就一直啟用著,那看下它的處理。
public void run() {try {mainLoop(); //主要實現內容} finally {// Someone killed this Thread, behave as if Timer cancelledsynchronized(queue) {newTasksMayBeScheduled = false;queue.clear(); // Eliminate obsolete references}}}/*** 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//如果隊列為空并且是有標志位,則等待。沒有標志位的情況為不在需要執行timer了,比如cancel或被gc的時候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 thinglong currentTime, executionTime;//分別是當前時間、理論執行時間task = queue.getMin();//獲取就近的tasksynchronized(task.lock) {//如果該task已經被置為cancelled,則將它從隊列里面移出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) { // period表示該task是一次性的,用完就移出queue.removeMin();//移出task,這塊會有queue的重新排序task.state = TimerTask.EXECUTED;//更新狀態為執行中} else {//可重復執行的task操作,將重新計算下次執行時間,并重新排序 //重點,此處解釋為什么period分正負:區別schedule方法和scheduleAtFixedRate//如果是負數,則以當前時間為準,往后計算下次執行時間//如果是正數,則以理論時間為準,往后計算下次執行時間queue.rescheduleMin(task.period<0 ? currentTime - task.period: executionTime + task.period);}}}if (!taskFired) // 如果還沒到任務執行時間就處于等待queue.wait(executionTime - currentTime);}if (taskFired) // 到執行時間了//執行task中的run方法,而不是start方法,所以并不是另起一個線程進行操作task.run();} catch(InterruptedException e) {//如果是不能捕獲的異常,就會有風險了}}}關于queue中的removeMin()和rescheduleMin()方法如下:
void removeMin() {//前兩行賦值可能會將queue亂序,所以才會有fixDown重新排序queue[1] = queue[size];queue[size--] = null; // Drop extra reference to prevent memory leakfixDown(1);}//因為取的時候也是取下標為1的task進行操作,所以此次也是將下標為1的task重新賦時間,并排序void rescheduleMin(long newTime) {queue[1].nextExecutionTime = newTime;fixDown(1);}//和fixUp方法類似,該排序單獨看也并非嚴謹,但由于每次操作都會經歷,所以應該是準的吧!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 kidif (queue[k].nextExecutionTime <= queue[j].nextExecutionTime)break;TimerTask tmp = queue[j]; queue[j] = queue[k]; queue[k] = tmp;k = j;}}當timerTask調用了timerTask.cancel()操作時,也可以人為的將它從Timer隊列中清除掉,方法如下:
public int purge() {int result = 0;synchronized(queue) {for (int i = queue.size(); i > 0; i--) {if (queue.get(i).state == TimerTask.CANCELLED) {queue.quickRemove(i); //由于i取值時必然大于0,所以肯定能夠找到正常的數據result++;}}if (result != 0)queue.heapify();//重新排序}return result;}queue中的quickRemove和heapify方法:
void quickRemove(int i) { //只要簡單賦值就行了,最后排序交給heapify()assert i <= size;queue[i] = queue[size];queue[size--] = null; // Drop extra ref to prevent memory leak}void heapify() {for (int i = size/2; i >= 1; i--)fixDown(i); //在前面的篇幅中介紹過了}當然如果Timer也可以人為的取消,不在繼續定時任務。其方法如下:
public void cancel() {synchronized(queue) {thread.newTasksMayBeScheduled = false;queue.clear(); //會將隊列中的所有的task賦值為nullqueue.notify(); // 通知thread中的mainLoop進行break操作}}綜上,其實大部分流程就屢清楚了。順帶介紹下Timer中的threadReaper。代碼如下:
/*** This object causes the timer's task execution thread to exit* gracefully when there are no live references to the Timer object and no* tasks in the timer queue. It is used in preference to a finalizer on* Timer as such a finalizer would be susceptible to a subclass's* finalizer forgetting to call it.*/private final Object threadReaper = new Object() {protected void finalize() throws Throwable {synchronized(queue) {thread.newTasksMayBeScheduled = false;queue.notify(); // In case queue is empty.}}};重寫了finalize方法,該方法為GC時候調用,主要使Timer中的thread能夠優雅退出。
4、總結
優勢:可以實現比較輕量級的定時任務,而且很方便
劣勢:
①由于Timer只是創建了一個thread去執行queue中的task,那么就可能會出現上一個任務執行延遲了,會影響到下一個定時任務。
②在TimerThread#mainloop中,也可看到,如果拋出了InterruptedException之外無法捕獲到的異常時,mainloop就會中斷,Timer也就無法使用了。
?
? ? ? ? ??
總結
以上是生活随笔為你收集整理的java定时器-Timer和TimerTask详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: dropbox连接不上解决方法
- 下一篇: 传统教培机构搭建网校平台是否成必然的趋势