轉載位址,請珍惜作者的勞動成果,轉載請注明出處:http://www.open-open.com/lib/view/open1337176725619.html
如果你使用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内部的實作原理。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<code>import</code> <code>java.util.Timer;</code>
<code>import</code> <code>java.util.TimerTask;</code>
<code>import</code> <code>org.junit.Test;</code>
<code> </code>
<code>class</code> <code>TestTimerTask </code><code>extends</code> <code>TimerTask {</code>
<code> </code><code>@Override</code>
<code> </code><code>public</code> <code>void</code> <code>run() {</code>
<code> </code><code>System.out.println(</code><code>"TestTimerTask is running......"</code><code>);</code>
<code> </code><code>}</code>
<code>}</code>
<code>public</code> <code>class</code> <code>TimerTaskTest {</code>
<code> </code><code>@Test</code>
<code> </code><code>public</code> <code>void</code> <code>testTimerTask() {</code>
<code> </code><code>Timer timer = </code><code>new</code> <code>Timer();</code>
<code> </code><code>timer.schedule(</code><code>new</code> <code>TestTimerTask(), </code><code>0</code><code>, </code><code>10</code><code>);</code>
上面的代碼是一個典型的Timer&TimerTask的應用,下面先來看一下new Timer()幹了什麼事,其源碼如下:
public Timer(String name) {
thread.setName(name); //thread為TimerThread執行個體。
thread.start();
}
從上面的源代碼可以知道,建立Timer對象的同時也啟動了TimerThread線程。下面來看看TimerThread幹了什麼事:
<code>public</code> <code>void</code> <code>run() {</code>
<code> </code><code>try</code> <code>{</code>
<code> </code><code>mainLoop(); </code><code>//線程真正執行的代碼在這個私有方法中</code>
<code> </code><code>} </code><code>finally</code> <code>{</code>
<code> </code><code>// Someone killed this Thread, behave as if Timer cancelled</code>
<code> </code><code>synchronized</code><code>(queue) {</code>
<code> </code><code>newTasksMayBeScheduled = </code><code>false</code><code>;</code>
<code> </code><code>queue.clear(); </code><code>// Eliminate obsolete references</code>
<code> </code><code>}</code>
<code> </code><code>}</code>
接着來看看私有方法mainLoop()幹了什麼事:
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
<code>private</code> <code>void</code> <code>mainLoop() {</code>
<code> </code><code>while</code> <code>(</code><code>true</code><code>) {</code>
<code> </code><code>try</code> <code>{</code>
<code> </code><code>TimerTask task;</code>
<code> </code><code>boolean</code> <code>taskFired; </code><code>//是否已經到達Task的執行時間,如果已經到達,設定為true,否則置為false</code>
<code> </code><code>synchronized</code><code>(queue) {</code>
<code> </code><code>// Wait for queue to become non-empty</code>
<code> </code><code>while</code> <code>(queue.isEmpty() && newTasksMayBeScheduled)</code>
<code> </code><code>queue.wait(); </code><code>//由此可以看出,Timer通過wait & notify 方法安排線程之間的同步</code>
<code> </code><code>if</code> <code>(queue.isEmpty())</code>
<code> </code><code>break</code><code>; </code><code>// Queue is empty and will forever remain; die</code>
<code> </code><code>// Queue nonempty; look at first evt and do the right thing</code>
<code> </code><code>long</code> <code>currentTime, executionTime;</code>
<code> </code><code>task = queue.getMin();</code>
<code> </code><code>synchronized</code><code>(task.lock) {</code>
<code> </code><code>if</code> <code>(task.state == TimerTask.CANCELLED) {</code>
<code> </code><code>queue.removeMin();</code>
<code> </code><code>continue</code><code>; </code><code>// No action required, poll queue again</code>
<code> </code><code>}</code>
<code> </code><code>currentTime = System.currentTimeMillis();</code>
<code> </code><code>executionTime = task.nextExecutionTime;</code>
<code> </code><code>if</code> <code>(taskFired = (executionTime<=currentTime)) { </code><code>//Task的執行時間已到,設定taskFired為true</code>
<code> </code><code>if</code> <code>(task.period == </code><code>0</code><code>) { </code><code>// Non-repeating, remove</code>
<code> </code><code>queue.removeMin(); </code><code>//移除隊列中的目前任務</code>
<code> </code><code>task.state = TimerTask.EXECUTED;</code>
<code> </code><code>} </code><code>else</code> <code>{ </code><code>// Repeating task, reschedule</code>
<code> </code><code>queue.rescheduleMin( </code><code>//重新設定任務的下一次執行時間</code>
<code> </code><code>task.period<</code><code>0</code> <code>? currentTime - task.period</code>
<code> </code><code>: executionTime + task.period);</code>
<code> </code><code>}</code>
<code> </code><code>}</code>
<code> </code><code>if</code> <code>(!taskFired) </code><code>// Task hasn't yet fired; wait</code>
<code> </code><code>queue.wait(executionTime - currentTime); </code><code>//還沒有執行時間,通過wait等待特定時間</code>
<code> </code><code>}</code>
<code> </code><code>if</code> <code>(taskFired) </code><code>// Task fired; run it, holding no locks</code>
<code> </code><code>task.run(); </code><code>//已經到達執行時間,執行任務</code>
<code> </code><code>} </code><code>catch</code><code>(InterruptedException e) {</code>
也就是說,一旦建立了Timer類的執行個體,就一直存在一個循環在周遊queue中的任務,如果有任務的話,就通過thread去執行該任務,否則線程通過wait()方法阻塞自己,由于沒有任務在隊列中,就沒有必要繼續thread中的循環。
上面提到,如果Timer的任務隊列中不包含任務時,Timer中的TimerThread線程并不會執行,接着來看看為Timer添加任務後會出現怎樣的情況。為Timer添加任務就是timer.schedule()幹的事,schedule()方法直接調用Timer的私有方法 sched(),sched()是真正安排Task的地方,其源代碼如下:
<code>private</code> <code>void</code> <code>sched(TimerTask task, </code><code>long</code> <code>time, </code><code>long</code> <code>period) {</code>
<code> </code><code>if</code> <code>(time < </code><code>0</code><code>)</code>
<code> </code><code>throw</code> <code>new</code> <code>IllegalArgumentException(</code><code>"Illegal execution time."</code><code>);</code>
<code> </code><code>synchronized</code><code>(queue) {</code>
<code> </code><code>if</code> <code>(!thread.newTasksMayBeScheduled)</code>
<code> </code><code>throw</code> <code>new</code> <code>IllegalStateException(</code><code>"Timer already cancelled."</code><code>);</code>
<code> </code><code>synchronized</code><code>(task.lock) {</code>
<code> </code><code>if</code> <code>(task.state != TimerTask.VIRGIN) </code><code>//我喜歡virgin狀态,其他狀态表明該Task已經被schedule過了</code>
<code> </code><code>throw</code> <code>new</code> <code>IllegalStateException(</code>
<code> </code><code>"Task already scheduled or cancelled"</code><code>);</code>
<code> </code>
<code> </code><code>//設定Task下一次應該執行的時間, 由System.currentTimeMillis()+/-delay得到</code>
<code> </code><code>task.nextExecutionTime = time; </code>
<code> </code><code>task.period = period;</code>
<code> </code><code>task.state = TimerTask.SCHEDULED;</code>
<code> </code><code>queue.add(task); </code><code>//queue為TaskQueue類的執行個體,添加任務到隊列中</code>
<code> </code><code>if</code> <code>(queue.getMin() == task) </code><code>//擷取隊列中nextExecutionTime最小的任務,如果與目前任務相同</code>
<code> </code><code>queue.notify(); </code><code>//還記得前面看到的queue.wait()方法麼</code>
不要奇怪,為什麼要判斷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的實作原理,修改這種實作方式也就非常簡單了。
本文轉自demoblog部落格園部落格,原文連結http://www.cnblogs.com/0616--ataozhijia/p/3710599.html如需轉載請自行聯系原作者
demoblog