java Timer(定時調用、實現固定時間執行)

 

  最近須要用到定時調用的功能。能夠經過java的Timer類來進行定時調用,下面是有關Timer的一些相關知識。java

  其實就Timer來說就是一個調度器,而TimerTask呢只是一個實現了run方法的一個類,而具體的TimerTask須要由你本身來實現,例如這樣:api

 

Timer timer = new Timer();
timer.schedule(new TimerTask() {
        public void run() {
            System.out.println("11232");
        }
}, 200000 , 1000);

 

 
  這裏直接實現一個TimerTask(固然,你能夠實現多個TimerTask,多個TimerTask能夠被一個Timer會被分配到多個 Timer中被調度,後面會說到Timer的實現機制就是說內部的調度機制),而後編寫run方法,20s後開始執行,每秒執行一次,固然你經過一個 timer對象來操做多個timerTask,其實timerTask自己沒什麼意義,只是和timer集合操做的一個對象,實現它就必然有對應的run 方法,以被調用,他甚至於根本不須要實現Runnable,由於這樣每每混淆視聽了,爲何呢?也是本文要說的重點。

  在說到timer的原理時,咱們先看看Timer裏面的一些常見方法:數組

一、這個方法是調度一個task,通過delay(ms)後開始進行調度,僅僅調度一次。安全

    public void schedule(TimerTask task, long delay)

 

二、在指定的時間點time上調度一次。數據結構

public void schedule(TimerTask task, Date time)

 

三、這個方法是調度一個task,在delay(ms)後開始調度,每次調度完後,最少等待period(ms)後纔開始調度。
public void schedule(TimerTask task, long delay, long period)

 

四、和上一個方法相似,惟一的區別就是傳入的第二個參數爲第一次調度的時間。
public void schedule(TimerTask task, Date firstTime, long period)

 

五、調度一個task,在delay(ms)後開始調度,而後每通過period(ms)再次調度, 貌似和方法:schedule是同樣的,其實否則,後面你會根據源碼看到,schedule在計算下一次執行的時間的時候,是經過當前時間(在任務執行前獲得) + 時間片,而 scheduleAtFixedRate方法是經過當前須要執行的時間(也就是計算出如今應該執行的時間)+ 時間片,前者是運行的實際時間,然後者是理論時間點,例如: schedule時間片是5s,那麼理論上會在 五、十、1五、20這些時間片被調度,可是若是因爲某些CPU徵用致使未被調度,假如等到第8s才被第一次調度,那麼 schedule方法計算出來的下一次時間應該是第13s而不是第10s,這樣有可能下次就越到20s後而 被少調度一次或屢次,而 scheduleAtFixedRate方法就是每次理論計算出下一次須要調度的時間用以排序,若第8s被調度,那麼計算出應該是第10s,因此它距離當前時間是2s,那麼再調度隊列排序中,會被優先調度,那麼就 儘可能減小漏掉調度的狀況。
public void scheduleAtFixedRate(TimerTask task, long delay, long period)

 

六、方法同上,惟一的區別就是第一次調度時間設置爲一個Date時間,而不是當前時間的一個時間片,咱們在源碼中會詳細說明這些內容。
public void scheduleAtFixedRate(TimerTask task, Date firstTime,long period)

 


源碼部分
 
 
首先看Timer的 構造方法有幾種:
構造方法1: 無參構造方法,簡單經過Tiemer爲前綴構造一個線程名稱:
public Timer() {
    this("Timer-" + serialNumber());
}

   建立的線程不爲主線程,則主線程結束後,timer自動結束,而無需使用cancel來完成對timer的結束。ide

 
構造方法2: 傳入了是否爲後臺線程,後臺線程當且僅當進程結束時,自動註銷掉。
public Timer(boolean isDaemon) {
    this("Timer-" + serialNumber(), isDaemon);
}

   另外兩個構造方法負責傳入名稱和將timer啓動:ui

public Timer(String name, boolean isDaemon) {
      thread.setName(name);
      thread.setDaemon(isDaemon);
      thread.start();
  }

   這裏有一個thread,這個thread很明顯是一個線程,被包裝在了Timer類中,咱們看下這個thread的定義是:this

private TimerThread thread = new TimerThread(queue);

 

  而定義TimerThread部分的是:
而定義TimerThread部分的是:

   看到這裏知道了,Timer內部包裝了一個線程,用來作獨立於外部線程的調度,而TimerThread是一個default類型的,默認狀況下是引用不到的,是被Timer本身所使用的。spa

 
 
  接下來看下有那些屬性
  除了上面提到的thread,還有一個很重要的屬性是:
private TaskQueue queue = new TaskQueue();

   看名字就知道是一個隊列,隊列裏面能夠先猜猜看是什麼,那麼大概應該是我要調度的任務吧,先記錄下了,接下來繼續向下看:線程

  裏面還有一個屬性是: threadReaper, 它是Object類型,只是重寫了finalize方法而已,是爲了垃圾回收的時候,將相應的信息回收掉,作GC的回補,也就是當timer線程因爲某種 緣由死掉了,而未被cancel,裏面的隊列中的信息須要清空掉,不過咱們一般是不會考慮這個方法的,因此知道java寫這個方法是幹什麼的就好了。
 
   接下來看調度方法的實現:
  對於上面6個調度方法,咱們不作一一列舉,爲何等下你就知道了:

  來看下方法:

public void schedule(TimerTask task, long delay)

   的源碼以下:

1 public void schedule(TimerTask task, long delay) {
2        if (delay < 0)
3            throw new IllegalArgumentException("Negative delay.");
4        sched(task, System.currentTimeMillis()+delay, 0);
5    }

   這裏調用了另外一個方法,將task傳入,第一個參數傳入System.currentTimeMillis()+delay可見爲第一次須要執行的時間的 時間點了(若是傳入Date,就是對象.getTime()便可,因此傳入Date的幾個方法就不用多說了),而第三個參數傳入了0,這裏能夠猜下要麼是 時間片,要麼是次數啥的,不過等會就知道是什麼了;另外關於方法:sched的內容咱們不着急去看他,先看下重載的方法中是如何作的

 
  再看看方法:
public void schedule(TimerTask task, long delay,long period)

   源碼爲:

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);
    }

   看來也調用了方法sched來完成調度,和上面的方法惟一的調度時候的區別是增長了傳入的period,而第一個傳入的是0,因此肯定這個參數爲時間片, 而不是次數,注意這個裏的period加了一個負數,也就是取反,也就是咱們開始傳入1000,在調用sched的時候會變成-1000,其實最終閱讀完 源碼後你會發現這個算是老外對於一種數字的理解,而並不是有什麼特殊的意義,因此閱讀源碼的時候也有這些困難所在。

 
  最後再看個方法是:
public void scheduleAtFixedRate(TimerTasktask,long delay,long period)

   源碼爲:

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);
   }

   惟一的區別就是在period沒有取反,其實你最終閱讀完源碼,上面的取反沒有什麼特殊的意義,老外不想增長一個參數來表示 scheduleAtFixedRate,而scheduleAtFixedRate和schedule的大部分邏輯代碼一致,所以用了參數的範圍來做爲 區分方法,也就是當你傳入的參數不是正數的時候,你調用schedule方法正好是獲得scheduleAtFixedRate的功能,而調用 scheduleAtFixedRate方法的時候獲得的正好是schedule方法的功能,呵呵,這些討論沒什麼意義,討論實質和重點:

   來看sched方法的實現體:

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)
                    throw new IllegalStateException(
                        "Task already scheduled or cancelled");
                task.nextExecutionTime = time;
                task.period = period;
                task.state = TimerTask.SCHEDULED;
            }
 
            queue.add(task);
            if (queue.getMin() == task)
                queue.notify();
        }
    }

   queue爲一個隊列,咱們先不看他數據結構,看到他在作這個操做的時候,發生了同步,因此在timer級別,這個是線程安全的,最後將task相關的參數賦值,主要包含nextExecutionTime(下一次執行時間),period(時間片),state(狀態),而後將它放入queue隊列中,作一次notify操做,爲何要作notify操做呢?看了後面的代碼你就知道了。

 

  簡言之,這裏就是講task放入隊列queue的過程,此時,你可能對queue的結構有些興趣,那麼咱們先來看看queue屬性的結構TaskQueue:

class TaskQueue {
 
    private TimerTask[] queue = new TimerTask[128];
 
    private int size = 0;

   可見,TaskQueue的結構很簡單,爲一個數組,加一個size,有點像ArrayList,是否是長度就128呢,固然不 是,ArrayList能夠擴容,它能夠,只是會形成內存拷貝而已,因此一個Timer來說,只要內部的task個數不超過128是不會形成擴容的;內部 提供了add(TimerTask)、size()、getMin()、get(int)、removeMin()、quickRemove(int)、 rescheduleMin(long newTime)、isEmpty()、clear()、fixUp()、fixDown()、heapify();

   


實踐部分
 
一、經過繼承TimerTask的方式實現
  必須重寫run方法.
public class MyTask extends TimerTask
{

    @Override
    public void run()
    {
        SimpleDateFormat sdf = null;
        sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
        System.out.println("當前時間:" + sdf.format(new Date()));
        
    }
    
}

 

public class TestTask
{
    public static void main(String[] args)
    {
        Timer t = new Timer(); // 創建Timer對象
        MyTask task = new MyTask(); //定義任務
        t.schedule(task, 1000,2000);//設置任務的執行,1秒後開始,每2秒執行一次
        
        Calendar cal = Calendar.getInstance();
        cal.set(Calendar.MINUTE, 30);
        
        t.schedule(task, cal.getTime() , 2000);

        
    }
}

 

 

二、經過匿名內部類實現

Timer timer = new Timer();  
        timer.scheduleAtFixedRate(new TimerTask() {  
                public void run() {  
                    
                    System.out.println("abc");  
                }  
        }, 1000 , 1000);

 

 

   致謝:感謝您的耐心閱讀!

相關文章
相關標籤/搜索