細說java.util.Timer,java.util.timer

來源:互聯網
上載者:User

細說java.util.Timer,java.util.timer

Timer是用於管理在後台執行的延遲任務或週期性任務,其中的任務使用java.util.TimerTask表示。任務的執行方式有兩種:

  • 按固定速率執行:即scheduleAtFixedRate的兩個重載方法
  • 按固定順延強制:即schedule的4個重載方法

具體差別會在後面詳細說明。

我們要實現一個定時任務,只需要實現TimerTask的run方法即可。每一個任務都有下一次執行時間nextExecutionTime(毫秒),如果是周期性的任務,那麼每次執行都會更新這個時間為下一次的執行時間,當nextExecutionTime小於目前時間時,都會執行它。

一、使用方式

Timer的具體使用方法非常簡單,比如:

        Timer timer = new Timer();        timer.schedule(new TimerTask() {            @Override            public void run() {                System.out.println("Timer is running");            }        }, 2000);
上面這個定時任務表示在2秒後開始執行,只執行一次。當然還可以執行循環性任務,只需要添加schedule的第三個參數period,如:

        Timer timer = new Timer();        timer. scheduleAtFixedRate(new TimerTask() {            @Override            public void run() {                System.out.println("Timer is running");            }        }, 2000, 5000);
表示2秒後開始執行,然後每隔5秒執行一次。

二、具體實現的分析

對於每一個Timer,後台只使用一個線程來執行所有的任務。而所有的任務都儲存到一個任務隊列java.util.TaskQueue中,它是Timer的一個內部類,這是一個優先順序隊列,使用的演算法是最小二元堆積(binary min heap)。

Timer的文檔中說:

After the last live reference to a Timer object goes away and all outstanding tasks have completed execution, the timer's task execution thread terminates gracefully (and becomes subject to garbage collection). 

意思是(沒有完全按照原文翻譯):當任務隊列中所有的任務都執行完後,即沒有一次性執行的任務,也沒有周期性的任務,那麼Timer的後台線程將優雅地終止,並成為記憶體回收的對象。

但是在測試時卻發現,在執行完一個一次性任務後,任務線程卻不終止,而是一直阻塞,這不知道是何原因,使用的JDK是1.7.0_79。

使用jstack查看到這個線程的狀態如下:

"Timer-0" prio=5 tid=0x00007ff184046000 nid=0x5807 in Object.wait() [0x000000011ebea000]   java.lang.Thread.State: WAITING (on object monitor)at java.lang.Object.wait(Native Method)- waiting on <0x00000007ab1bbea0> (a java.util.TaskQueue)at java.lang.Object.wait(Object.java:503)at java.util.TimerThread.mainLoop(Timer.java:526)- locked <0x00000007ab1bbea0> (a java.util.TaskQueue)at java.util.TimerThread.run(Timer.java:505)

可以看到Timer任務線程處理阻塞狀態。

一個TimerTask有四種狀態:

  • VIRGIN:新建立的任務的狀態,表示還未安排執行
  • SCHEDULED:已安排執行,對於非周期性的任務來說,表示還未執行,當把任務添加到任務執行隊列時的狀態,即調用Timer.schedule時
  • EXECUTED:表示非週期性任務已經執行或正在執行中,並且還未被取消
  • CANCELLED:表示任務已經取消,當調用cancel方法後即為該狀態,該狀態的任務會在每次執行時被移出隊列

下面看看當調用Timer.schedule時發生了什麼,所有調用Timer.schedule都是調用的一個私人方法sched。

比如順延強制的任務(非週期性任務):

    public void schedule(TimerTask task, long delay) {        // 延遲不能小於0        if (delay < 0)            throw new IllegalArgumentException("Negative delay.”);        // 調用私人的sched方法,注意這裡的執行時間是絕對時間,而period為0表示不是週期性任務        sched(task, System.currentTimeMillis()+delay, 0);    }    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) {                // 如果要執行的任務狀態不是VIRGIN,那麼拋出異常                if (task.state != TimerTask.VIRGIN)                    throw new IllegalStateException(                        "Task already scheduled or cancelled”);                // 設定任務的執行時間,注意這個時間是絕對時間                task.nextExecutionTime = time;                // 設定任務執行的周期                task.period = period;                // 設定任務的狀態為SCHEDULED                task.state = TimerTask.SCHEDULED;            }            // 把任務添加到任務隊列,等任務執行線程調試執行            queue.add(task);            // 檢查下一個將要執行的任務是不是當前添加的任務,如果是則通知背景工作線程            if (queue.getMin() == task)                queue.notify();        }    }
執行任務的後台線程,Timer使用了一個內部類TimerThread,它有一個newTasksMayBeScheduled屬性工作表示是否還能執行任務,預設為true,當調用cancel方法時設定為false,之後將不能再添加或執行任務。同時它還持有一個任務隊列的引用,而不是直接引用Timer的任務隊列,這是因為為了防止循環參考導致Timer無法被記憶體回收,以及任務線程不能正常終止。

下面看一下這個任務線程的實現:

    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;                synchronized(queue) {                    // 等待任務隊列變成非空,如果任務隊列為空白並且Timer未取消,就阻塞線程,等待任務隊列為非空,從前面可以看到,每次添加完一個任務後,會通知這個線程來檢查執行相應的任務                    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)) {                        // 如果不是非週期性任務,那麼從任務隊列中移除該任務,並把該任務的狀態設定為EXECUTED                            if (task.period == 0) { // Non-repeating, remove                                queue.removeMin();                                task.state = TimerTask.EXECUTED;                            } else { // Repeating task, reschedule                                // 如果是週期性任務,則重新設定任務的下一次執行時間,這裡要注意period有可能為正和負                                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) {            }        }    }

其中重新設定任務的下一次執行時間的邏輯:

queue.rescheduleMin(task.period<0 ? currentTime   - task.period : executionTime + task.period);
這裡任務執行循環可為正和負:

  • 正數:表示按照固定的速率調度執行,比如執行循環是每5秒執行一次,如上一次執行時間是20:51:30,那麼下一次執行時間就為20:51:35,如果由於執行其他任務的時間超過5秒,比如用了15秒,那麼這有可能會導致這種任務不能夠在指定的時間執行,這就破壞了任務執行的速率(rate),但是會在後面連續執行3次。
  • 負數:表示按照固定的延遲來調度執行,比如執行循環是每5秒執行一次,在正常情況下,如它的執行時間是20:51:30,但是由於執行其他任務時間花了8秒秒,即執行到當前任務時是20:51:38,那麼它的下一次執行時間將向後延遲,即20:51:43。

三、Timer的缺陷

1、由於執行任務的線程只有一個,所以如果某個任務的執行時間過長,那麼將破壞其他任務的定時精確性。如一個任務每1秒執行一次,而另一個任務執行一次需要5秒,那麼如果是固定速率的任務,那麼會在5秒這個任務執行完成後連續執行5次,而固定延遲的任務將丟失4次執行。

2、如果執行某個任務過程中拋出了異常,那麼執行線程將會終止,導致Timer中的其他任務也不能再執行。

3、Timer使用的是絕對時間,即是某個時間點,所以它執行依賴系統的時間,如果系統時間修改了的話,將導致任務可能不會被執行。

四、更好的替代方法

由於Timer存在上面說的這些缺陷,在JDK1.5中,我們可以使用ScheduledThreadPoolExecutor來代替它,使用Executors.newScheduledThreadPoolFactory 方法或使用ScheduledThreadPoolExecutor的建構函式來建立定時任務,它是基於線程池的實現,不會存在Timer存在的上述問題,當線程數量為1時,它相當於Timer。

著作權聲明:本文為博主原創文章,未經博主允許不得轉載。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.