Java Timer&TimerTask原理分析

openkk 12年前發布 | 77K 次閱讀 Java Java開發

如果你使用Java語言進行開發,對于定時執行任務這樣的需求,自然而然會想到使用Timer和TimerTask完成任務,我最近就使用 Timer和TimerTask完成了一個定時執行的任務,實現得沒有問題,但當在TimerTaks的run()方法中使用 Thread.sleep()方式時,可能會出現奇怪的現象,好像Timer失效了,網上查了一下,倒是有人遇到了相同的問題,但是并沒有找到一篇解釋為什么會出現這種情況,期待有某位達人能夠分析清楚這個問題。

 

遇到了這樣的問題,始終讓我不爽,于是看了一下Timer的源碼,先將了解到的內容整理如下,接下來再看看Thread.sleep()的源碼,看能否找到問題所在。

 

在Java中,與定時任務執行相關的類很少,只有Timer、TimerTask、TimerThread、TaskQueue幾個,其中每個類的職責大致如下:

Timer:一個Task的調度類,和TimerTask一樣,暴露給最終用戶使用的類,通過schedule方法安排Task的執行計劃。該類通過TaskQueue和TimerThread類完成Task的調度。

TimerTask:實現Runnable接口,表明每一個任務均為一個獨立的線程。通過run()方法提供用戶定制自己任務。該類有一個比較重要的成員變量nextExecutionTime ,表示下一次執行該任務的時間。以后會看到,Timer機制就是靠這個值安排Task執行的。

TimerThread:繼承于Thread,是真正執行Task的類。

TaskQueue:一個存儲Task的數據結構,內部由一個最小堆實現,堆的每個成員為一個TimeTask,每個Task依靠其 nextExecutionTime值進行排序,也就是說,nextExecutionTime最小的任務在隊列的最前端,從而能夠現實最早執行。

 

要想使用Timer,用戶只需要了解Timer和TimerTask,下面現已一個最基本的Timer和TimerTask使用案例入手,來看一下Timer內部的實現原理。

import java.util.Timer;

import java.util.TimerTask;

import org.junit.Test;



class TestTimerTask extends TimerTask {

  @Override

  public void run() {

    System.out.println("TestTimerTask is running......");

  }

}

public class TimerTaskTest {

  @Test

  public void testTimerTask() {

    Timer timer = new Timer();

    timer.schedule(new TestTimerTask(), 0, 10);

  }

}

 

上面的代碼是一個典型的Timer&TimerTask的應用,下面先來看一下new Timer()干了什么事,其源碼如下:

public Timer(String name) {

        thread.setName(name);    //thread為TimerThread實例。

        thread.start();

}

從上面的源代碼可以知道,創建Timer對象的同時也啟動了TimerThread線程。下面來看看TimerThread干了什么事:

public void run() {

        try {

            mainLoop();                 //線程真正執行的代碼在這個私有方法中

        } finally {

            // Someone killed this Thread, behave as if Timer cancelled

            synchronized(queue) {

                newTasksMayBeScheduled = false;

                queue.clear();  // Eliminate obsolete references

            }

        }

}

接著來看看私有方法mainLoop()干了什么事:

private void mainLoop() {

        while (true) {

            try {

                TimerTask task;

                boolean taskFired;       //是否已經到達Task的執行時間,如果已經到達,設置為true,否則置為false

                synchronized(queue) {

                    // Wait for queue to become non-empty

                    while (queue.isEmpty() && newTasksMayBeScheduled)

                        queue.wait();                //由此可以看出,Timer通過wait & notify 方法安排線程之間的同步

                    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)) {        //Task的執行時間已到,設置taskFired為true

                            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);    //還沒有執行時間,通過wait等待特定時間

                }

                if (taskFired)  // Task fired; run it, holding no locks

                    task.run();    //已經到達執行時間,執行任務

            } catch(InterruptedException e) {

            }

        }

}

也就是說,一旦創建了Timer類的實例,就一直存在一個循環在遍歷queue中的任務,如果有任務的話,就通過thread去執行該任務,否則線程通過wait()方法阻塞自己,由于沒有任務在隊列中,就沒有必要繼續thread中的循環。

 

上面提到,如果Timer的任務隊列中不包含任務時,Timer中的TimerThread線程并不會執行,接著來看看為Timer添加任務后會出現怎樣的情況。為Timer添加任務就是timer.schedule()干的事,schedule()方法直接調用Timer的私有方法 sched(),sched()是真正安排Task的地方,其源代碼如下:

private void sched(TimerTask task, long time, long period) {

        if (time < 0)

            throw new IllegalArgumentException("Illegal execution time.");



        synchronized(queue) {

            if (!thread.newTasksMayBeScheduled)

                throw new IllegalStateException("Timer already cancelled.");



            synchronized(task.lock) {

                if (task.state != TimerTask.VIRGIN)             //我喜歡virgin狀態,其他狀態表明該Task已經被schedule過了

                    throw new IllegalStateException(

                        "Task already scheduled or cancelled");



                 //設置Task下一次應該執行的時間, 由System.currentTimeMillis()+/-delay得到

                task.nextExecutionTime = time;               

                task.period = period;

                task.state = TimerTask.SCHEDULED;

            }



            queue.add(task);            //queue為TaskQueue類的實例,添加任務到隊列中

            if (queue.getMin() == task)        //獲取隊列中nextExecutionTime最小的任務,如果與當前任務相同

                queue.notify();                         //還記得前面看到的queue.wait()方法么

        }

}

不要奇怪,為什么要判斷queue.getMin() == task時,才通過queue.notify()恢復執行。因為這種方式已經滿足所有的喚醒要求了。

如果安排當前Task之前queue為空,顯然上述判斷為true,于是mainLoop()方法能夠繼續執行。

如果安排當前Task之前queue不為空,那么mainLoop()方法不會一直被阻塞,不需要notify方法調用。

調用該方法還有一個好處是,如果當前安排的Task的下一次執行時間比queue中其余Task的下一次執行時間都要小,通過notify方法可以提前打開queue.wait(executionTime - currentTime)方法對mainLoop()照成的阻塞,從而使得當前任務能夠被優先執行,有點搶占的味道。

 

 上述分析可以看出,Java中Timer機制的實現僅僅使用了JDK中的方法,通過wait & notify機制實現,其源代碼也非常簡單,但可以想到的是這種實現機制會對開發者造成一種困擾,sched()方法中可以看出,對于一個重復執行的任務,Timer的實現機制是先安排Task下一次執行的時間,然后再啟動Task的執行,如果Task的執行時間大于下一次執行的間隔時間,可能出現不可預期的錯誤。當然,了解了Timer的實現原理,修改這種實現方式也就非常簡單了。

 本文由用戶 openkk 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!