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);}}結(jié)果:
hello world hello world hello world hello world hello world hello world hello world2、類講解
TimerTask.java:主要為任務(wù)的具體內(nèi)容。
Timer.java中含有3個(gè)類:Timer、TimerThread、TaskQueue。
三者關(guān)系為:TaskQueue中存放一些列將要執(zhí)行的TimerTask,以數(shù)組的形式存放,下標(biāo)約小(注:下標(biāo)為0不處理,即使用的最小下標(biāo)為1),則表明優(yōu)先級(jí)越高。
TimerThread為T(mén)hread的擴(kuò)展類,會(huì)一直從TaskQueue中獲取下標(biāo)為1的TimerTask進(jìn)行執(zhí)行。并根據(jù)該TimerTask是否需要重復(fù)執(zhí)行來(lái)決定是否放回到TaskQueue中。
Timer用于配置用戶期望的任務(wù)執(zhí)行時(shí)間、執(zhí)行次數(shù)、執(zhí)行內(nèi)容。它內(nèi)部會(huì)配置TimerThread、TaskQueue。
3、源碼解讀
Timer類下的方法如下:
Timer中涉及到4個(gè)成員變量,queue、thread已經(jīng)在上面介紹過(guò)了,對(duì)于nextSerialNumber,只是用于命名默認(rèn)的thread名稱使用。threadReaper為了在GC時(shí)進(jìn)行相關(guān)處理,后面再介紹。
Timer的構(gòu)造函數(shù)實(shí)現(xiàn)大同小異,以Timer(String,boolean)為例:
public Timer(String name, boolean isDaemon) {thread.setName(name); //設(shè)置成員變量的線程名稱thread.setDaemon(isDaemon); //該線程是否為守護(hù)線程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); //最后調(diào)用了sched方法}這一塊有困惑的可能是為什么period取了負(fù)數(shù)?而且所有的schedule(...)方法,最后傳給sched中的period都是負(fù)的。之后再介紹。
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); //也是調(diào)用sched方法}此時(shí)會(huì)發(fā)現(xiàn)schedule與scheduleAtFixedRate似乎區(qū)別不大,唯一有區(qū)別的是schedule最后傳值給sched的period是負(fù)數(shù),而scheduleAtFixedRate傳的是正數(shù)。
在schedule方法的注釋上,有這段內(nèi)容:
* <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).翻譯:如果出現(xiàn)某一次任務(wù)的延遲,那么之后的任務(wù)都會(huì)以period為周期進(jìn)行延遲。
而scheduleAtFixedRate方法也有對(duì)應(yīng)注釋:
* <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).翻譯:每次的執(zhí)行都是以初始時(shí)計(jì)算好的時(shí)間為準(zhǔn),如果出現(xiàn)某次任務(wù)的延遲,則之后的任務(wù)會(huì)快速執(zhí)行,即按計(jì)劃時(shí)間執(zhí)行。
那么看下Sched()方法實(shí)現(xiàn):
private void sched(TimerTask task, long time, long period) {//接收具體任務(wù),第一次執(zhí)行時(shí)間,周期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放到隊(duì)列中,并進(jìn)行隊(duì)列排序if (queue.getMin() == task)//如果隊(duì)列里恰好下標(biāo)為1的任務(wù)為當(dāng)前的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進(jìn)行排序,排序并不是十分嚴(yán)謹(jǐn),將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() { //注意最小的值是從下標(biāo)為1的獲取,queue[0]其實(shí)沒(méi)用到return queue[1];}數(shù)據(jù)已經(jīng)放到queue中了,那么看下是什么時(shí)候執(zhí)行的。在之前Timer的構(gòu)造函數(shù)這塊,有一句是:thread.start();說(shuō)明TimerThread在Timer初始化之后就一直啟用著,那看下它的處理。
public void run() {try {mainLoop(); //主要實(shí)現(xiàn)內(nèi)容} 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//如果隊(duì)列為空并且是有標(biāo)志位,則等待。沒(méi)有標(biāo)志位的情況為不在需要執(zhí)行timer了,比如cancel或被gc的時(shí)候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;//分別是當(dāng)前時(shí)間、理論執(zhí)行時(shí)間task = queue.getMin();//獲取就近的tasksynchronized(task.lock) {//如果該task已經(jīng)被置為cancelled,則將它從隊(duì)列里面移出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,這塊會(huì)有queue的重新排序task.state = TimerTask.EXECUTED;//更新?tīng)顟B(tài)為執(zhí)行中} else {//可重復(fù)執(zhí)行的task操作,將重新計(jì)算下次執(zhí)行時(shí)間,并重新排序 //重點(diǎn),此處解釋為什么period分正負(fù):區(qū)別schedule方法和scheduleAtFixedRate//如果是負(fù)數(shù),則以當(dāng)前時(shí)間為準(zhǔn),往后計(jì)算下次執(zhí)行時(shí)間//如果是正數(shù),則以理論時(shí)間為準(zhǔn),往后計(jì)算下次執(zhí)行時(shí)間queue.rescheduleMin(task.period<0 ? currentTime - task.period: executionTime + task.period);}}}if (!taskFired) // 如果還沒(méi)到任務(wù)執(zhí)行時(shí)間就處于等待queue.wait(executionTime - currentTime);}if (taskFired) // 到執(zhí)行時(shí)間了//執(zhí)行task中的run方法,而不是start方法,所以并不是另起一個(gè)線程進(jìn)行操作task.run();} catch(InterruptedException e) {//如果是不能捕獲的異常,就會(huì)有風(fēng)險(xiǎn)了}}}關(guān)于queue中的removeMin()和rescheduleMin()方法如下:
void removeMin() {//前兩行賦值可能會(huì)將queue亂序,所以才會(huì)有fixDown重新排序queue[1] = queue[size];queue[size--] = null; // Drop extra reference to prevent memory leakfixDown(1);}//因?yàn)槿〉臅r(shí)候也是取下標(biāo)為1的task進(jìn)行操作,所以此次也是將下標(biāo)為1的task重新賦時(shí)間,并排序void rescheduleMin(long newTime) {queue[1].nextExecutionTime = newTime;fixDown(1);}//和fixUp方法類似,該排序單獨(dú)看也并非嚴(yán)謹(jǐn),但由于每次操作都會(huì)經(jīng)歷,所以應(yīng)該是準(zhǔn)的吧!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;}}當(dāng)timerTask調(diào)用了timerTask.cancel()操作時(shí),也可以人為的將它從Timer隊(duì)列中清除掉,方法如下:
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取值時(shí)必然大于0,所以肯定能夠找到正常的數(shù)據(jù)result++;}}if (result != 0)queue.heapify();//重新排序}return result;}queue中的quickRemove和heapify方法:
void quickRemove(int i) { //只要簡(jiǎn)單賦值就行了,最后排序交給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); //在前面的篇幅中介紹過(guò)了}當(dāng)然如果Timer也可以人為的取消,不在繼續(xù)定時(shí)任務(wù)。其方法如下:
public void cancel() {synchronized(queue) {thread.newTasksMayBeScheduled = false;queue.clear(); //會(huì)將隊(duì)列中的所有的task賦值為nullqueue.notify(); // 通知thread中的mainLoop進(jìn)行break操作}}綜上,其實(shí)大部分流程就屢清楚了。順帶介紹下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.}}};重寫(xiě)了finalize方法,該方法為GC時(shí)候調(diào)用,主要使Timer中的thread能夠優(yōu)雅退出。
4、總結(jié)
優(yōu)勢(shì):可以實(shí)現(xiàn)比較輕量級(jí)的定時(shí)任務(wù),而且很方便
劣勢(shì):
①由于Timer只是創(chuàng)建了一個(gè)thread去執(zhí)行queue中的task,那么就可能會(huì)出現(xiàn)上一個(gè)任務(wù)執(zhí)行延遲了,會(huì)影響到下一個(gè)定時(shí)任務(wù)。
②在TimerThread#mainloop中,也可看到,如果拋出了InterruptedException之外無(wú)法捕獲到的異常時(shí),mainloop就會(huì)中斷,Timer也就無(wú)法使用了。
?
? ? ? ? ??
總結(jié)
以上是生活随笔為你收集整理的java定时器-Timer和TimerTask详解的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: dropbox连接不上解决方法
- 下一篇: 传统教培机构搭建网校平台是否成必然的趋势