本文主要介紹JKD自帶的 java.util.Timer定時器的實現原理.
如果你僅僅只是想知道如何在自己的程序中來使用java.util.Timer的一些方法,那么請移步:
http://blog.csdn.net/Twent/archive/2005/09/20/485528.aspx .
API介紹在這里: http://online.chinaitpower.com/api/jdk150/java/util/Timer.html
Timer中最主要由三個部分組成: 任務 TimerTask 、任務隊列:TaskQueue queue 和 任務調試者:TimerThread thread
他們之間的關系可以通過下面圖示:
在這個圖中,可以清楚地看到這Timer本身及其和這三個部分的關系:
1. Timer可以看作是面向開發人員的一個"接口"
2. 所有向Timer添加的任務都會被放入一個TaskQueue類型的任務隊列中去.(如何安排任務優先級順序下文會講)
3. 任務調度由TimerThread負責
任務單元 TimerTask
首先看一下任務單元實體類: TimerTask.
在這個類中, 要關注的是任務狀態和幾個狀態常量:
- /**標識任務的狀態*/
- int state=VIRGIN;
- /**任務的狀態的常量*/
- static final int VIRGIN= 0 ;
- static final int SCHEDULED= 1 ;
- static final int EXECUTED= 2 ;
- static final int CANCELLED= 3 ;
以及一個比較重要的兩個成員變量:
- long nextExecutionTime;
- long period= 0 ;
nextExecutionTime 這個成員變量用到記錄該任務下次執行時間, 其格式和System.currentTimeMillis()一致.
這個值是作為任務隊列中任務排序的依據.任務調試者執行每個任務前會對這個值作處理,重新計算下一次任務執行時間,并為這個變量賦值.
period 用來描述任務的執行方式: 0表示不重復執行的任務. 正數表示固定速率執行的任務. 負數表示固定延遲執行的任務.
(固定速率: 不考慮該任務上一次執行情況,始終從開始時間算起的每period執行下一次. 固定延遲: 考慮該任務一次執行情況,在上一次執行后period執行下一次 ).
任務隊列 TaskQueue
事實上任務隊列是一個數組, 采用平衡二叉堆來實現他的優先級調度, 并且是一個小頂堆.需要注意的是, 這個堆中 queue[n] 的孩子是 queue[2*n] 和 queue[2*n+1].
任務隊列的優先級按照TimerTask類的成員變量nextExecutionTime值來排序(注意, 這里的任務指的是那些交由定時器來執行的, 繼承TimerTask的對象).
在任務隊列中, nextExecutionTime最小就是所有任務中最早要被調度來執行的, 所以被安排在queue[1] (假設任務隊列非空).
對于堆中任意一個節點n, 和他的任意子孫節點d,一定遵循: n.nextExecutionTime <= d.nextExecutionTime.
[添加任務]
- void add(TimerTasktask){
- if (size+ 1 ==queue.length)
- queue=Arrays.copyOf(queue, 2 *queue.length);
- queue[++size]=task;
- fixUp(size);
- }
首先會判斷是否已經滿了,( 任務隊列的初始容量是128 ),如果已經滿了, 那么容量擴大至原來2倍, 然后將需要添加的任務放到隊列最后.
之后就會調用 fixUp 方法來進行隊列中任務優先級調整. fixUp方法的作用是盡量將隊列中指定位置(k)的任務向隊列前面移動, 即提高它的優先級. 因為新加入的方法很有可能比已經在任務隊列中的其它任務要更早執行.
- private void fixUp( int k){
- while (k> 1 ){
- int j=k>> 1 ; //對于正數,右移位<==>j=k/2,所以j的位置就是k的父親節點
- if (queue[j].nextExecutionTime<=queue[k].nextExecutionTime)
- break ;
- TimerTasktmp=queue[j];
- queue[j]=queue[k];
- queue[k]=tmp;
- k=j;
- }
- }
這個過程可以這個描述: 不斷地將k位置上元素和它的父親進行比較, 上文也提到過了. 由于必須滿足 "對于堆中任意一個節點n, 和他的任意子孫節點d,一定遵循: n.nextExecutionTime <= d.nextExecutionTime.", 那么在不斷比較過程中, 如果發現孩子節點比父親小的時候, 那么將父親和孩子位置互換. 直到來到隊列第一個位置.
[移除任務]
- void removeMin(){
- queue[ 1 ]=queue[size];
- queue[size--]= null ; //Dropextrareferencetopreventmemoryleak
- fixDown( 1 );
- }
從任務隊列中移除一個任務的過程, 首先直接將當前任務隊列中最后一個任務賦給queue[1], 然后將隊列中任務數量--, 最后和上面類似, 但是這里是調用fixDown(int k)方法了, 盡量將k位置的任務向隊列后面移動.
- /**
- *-將k位置的元素向堆底方向移動.<br>
- *1.j=k<<1,將j定位到兒子中.<br>
- *2.將j精確定位到較小的兒子.<br>
- *3.然后k與j比較,如果k大于j的話,那么互換<br>
- *4.繼續...
- */
- private void fixDown( int k){
- int j;
- //如果還沒有到隊列的最后,并且沒有溢出(j>0)
- //在沒有出現溢出的情況下,j=k<<1等價于j=2*k;
- while ((j=k<< 1 )<=size&&j> 0 ){
- //找到k的兩個孩子中小的那個.
- if (j<size&&queue[j].nextExecutionTime>queue[j+ 1 ].nextExecutionTime)
- j++; //jindexessmallestkid
- //找到這個較小的孩子后,(此時k是父親,j是較小的兒子),父親和兒子互換位置,即k和j換位子.這樣一直下去就可以將這個較大的queue[1]向下堆底移動了.
- if (queue[k].nextExecutionTime<=queue[j].nextExecutionTime)
- break ;
- TimerTasktmp=queue[j];
- queue[j]=queue[k];
- queue[k]=tmp;
- k=j;
- }
- }
下面來看看任務調度者是如何工作的.
任務調度 TimerThread
關于任務調度主要要講下一個成員變量 newTasksMayBeScheduled 和 調度方法 mainLoop().
- boolean newTasksMayBeScheduled= true ;
- private void mainLoop(){
- while ( true ){
- try {
- TimerTasktask;
- boolean taskFired= false ;
- synchronized (queue){
- while (queue.isEmpty()&&newTasksMayBeScheduled){
- queue.wait();
- }
- if (queue.isEmpty())
- break ; //直接挑出mainLoop了.
- long currentTime,executionTime;
- task=queue.getMin(); //獲取這個任務隊列第一個任務
- synchronized (task.lock){
- if (task.state==TimerTask.CANCELLED){
- queue.removeMin();
- continue ;
- }
- currentTime=System.currentTimeMillis();
- executionTime=task.nextExecutionTime;
- if (taskFired=(executionTime<=currentTime)){
- if (task.period== 0 ){ //Non-repeating,remove
- queue.removeMin();
- task.state=TimerTask.EXECUTED;
- } else { //Repeatingtask,reschedule
- queue.rescheduleMin(task.period< 0 ?currentTime-task.period:executionTime
- +task.period);
- }
- }
- } //釋放鎖
- if (!taskFired)
- queue.wait(executionTime-currentTime);
- }
- if (taskFired) //Taskfired;runit,holdingnolocks
- task.run();
- } catch (InterruptedExceptione){
- }
- } //while(true)
- }
newTasksMayBeScheduled變量用來表示是否需要繼續等待新任務了.
默認情況這個變量是
true
, 并且這個變量一直是true的,只有兩種情況的時候會變成
false
1.當調用Timer的cancel方法
2.沒有引用指向Timer對象了.
任務調度: mainLoop()方法中的一個while可以理解為一次任務調度:
STEP 1 : 判斷任務隊列中是否還有任務, 如果任務隊列為空了, 但是newTasksMayBeScheduled變量還是true, 表明需要繼續等待新任務, 所以一直等待.
STEP 2 : 等待喚醒后, 再次判斷隊列中是否有任務. 如果還是沒有任務,那么直接結束定時器工作了.
因為queue只在兩個地方被調用: addTask和cancel
1.向任務隊列中增加任務會喚醒
2.timer.cancel()的時候也會喚醒
那么這里如果還是empty,那么就是cancel的喚醒了,所以可以結束timer工作了.
STEP 3 : 從任務隊列中取出第一個任務,即nextExecutionTime最小的那個任務.
STEP 4: 判斷這個任務是否已經被取消. 如果已經被取消了,那么就直接從任務隊列中移除這個任務( removeMin() ),然后直接進入下一個任務調度周期.
STEP 5 : 判斷是否到了或者已經超過了這個任務應該執行的時間了.
如果到了
, 不會立即執行它,而是會在這次循環的最后來執行它.
這里做的事情可以看作是為下一個調度周期進行準備:包括:
1. 判斷是否是重復(repeating)任務,如果 task.period == 0, 那么就不是重復任務,所以可以直接將這個任務從任務隊列中移除了(
removeMin()
),因為沒有必要留到下一個調度周期中去了.
2. 如果是需要重復執行的任務, 那么就要重新設置這個任務的nextExecutionTime,即調用方法
queue.rescheduleMin(long)
,這個方法中會調用
fixDown(1)
負責重新調整任務隊列的優先級順序.
如果還沒有到執行時間 , 一直等到 queue.wait(executionTime - currentTime)
并且等待完畢后,似乎可以開始運行了, 但是這里設計成不立即運行,而是直接進入下一個任務調度周期 .(因為taskFired =false,所以不會在這次進行執行的.)
STEP: 6 開始調用任務的run方法運行任務.
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
