線程 Timer TimerTask 計時器 定時任務 MD

Markdown版本筆記 個人GitHub首頁 個人博客 個人微信 個人郵箱
MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina.com

線程 Timer TimerTask 計時器 定時任務java


目錄

Timer 計時器

Timer能夠定時(在指定時間執行任務)、延遲(延遲指定時間執行任務)、週期性地執行任務(每隔指定時間執行一次任務)。git

一種工具,線程用其安排之後在後臺線程中執行的任務。可安排任務執行一次,或者按期重複執行。
與每一個 Timer 對象相對應的是單個後臺線程,用於順序地執行全部計時器任務。計時器任務應該迅速完成。若是完成某個計時器任務的時間太長,那麼它會「獨佔」計時器的任務執行線程。所以,這就可能延遲後續任務的執行,而這些任務就可能「堆在一塊兒」,而且在上述不友好的任務最終完成時纔可以被快速連續地執行。github

對 Timer 對象最後的引用完成後,而且 全部未處理的任務都已執行完成後,計時器的任務執行線程會正常終止(而且成爲垃圾回收的對象)。可是這可能要很長時間後才發生。
默認狀況下,任務執行線程並不做爲守護線程來運行,因此它可以阻止應用程序終止。若是調用者想要快速終止計時器的任務執行線程,那麼調用者應該調用計時器的 cancel 方法。
若是意外終止了計時器的任務執行線程,例如調用了它的 stop 方法,那麼全部之後對該計時器安排任務的嘗試都將致使 IllegalStateException,就好像調用了計時器的 cancel 方法同樣。安全

此類是線程安全的:多個線程能夠共享單個 Timer 對象而無需進行外部同步。
此類不提供實時保證:它使用 Object.wait(long) 方法來安排任務。
實現注意事項:此類可擴展到大量同時安排的任務(存在數千個都沒有問題)。在內部,它使用二進制堆來表示其任務隊列,因此安排任務的開銷是 O(log n),其中 n 是同時安排的任務數。
實現注意事項:全部構造方法都啓動計時器線程。微信

構造方法ide

  • Timer() 建立一個新計時器。
  • Timer(String name) 建立一個新計時器,其相關的線程具備指定的名稱。
  • Timer(boolean isDaemon) 建立一個新計時器,能夠指定其相關的線程做爲守護程序運行。
  • Timer(String name, boolean isDaemon) 建立一個新計時器,其相關的線程具備指定的名稱,而且能夠指定做爲守護程序運行。

公共方法工具

  • void cancel() 終止此計時器,丟棄全部當前已安排的任務。
    • 這不會干擾當前正在執行的任務(若是存在)。
    • 一旦終止了計時器,那麼它的執行線程也會終止,而且沒法根據它安排更多的任務。
    • 注意,在此計時器調用的計時器任務的 run 方法內調用此方法,就能夠絕對確保正在執行的任務是此計時器所執行的最後一個任務。
    • 能夠重複調用此方法,可是第二次和後續調用無效。
  • int purge() 今後計時器的任務隊列中移除全部已取消的任務。
  • void schedule(TimerTask task, long delay) t安排在指定延遲後執行指定的任務。ask - 所要安排的任務,delay - 執行任務前的延遲時間(毫秒)。
  • void schedule(TimerTask task, Date time) 安排在指定的時間執行指定的任務。若是此時間已過去,則安排當即執行該任務。
  • void schedule(TimerTask task, long delay, long period) 安排指定的任務從指定的延遲後開始進行重複的固定延遲執行。以近似固定的時間間隔(由指定的週期分隔)進行後續執行。
    • 在固定延遲執行中,根據前一次執行的實際執行時間來安排每次執行。若是因爲任何緣由(如垃圾回收或其餘後臺活動)而延遲了某次執行,則後續執行也將被延遲。從長期來看,執行的頻率通常要稍慢於指定週期的倒數(假定 Object.wait(long) 所依靠的系統時鐘是準確的)。
    • 固定延遲執行適用於那些須要「平穩」運行的重複活動。換句話說,它適用於在短時間運行中保持頻率準確要比在長期運行中更爲重要的活動。這包括大多數動畫任務,如以固定時間間隔閃爍的光標。這還包括爲響應人類活動所執行的固定活動,如在按住鍵時自動重複輸入字符。
  • void schedule(TimerTask task, Date firstTime, long period) 安排指定的任務在指定的時間開始進行重複的固定延遲執行。
  • void scheduleAtFixedRate(TimerTask task, long delay, long period) 安排指定的任務在指定的延遲後開始進行重複的固定速率執行。以近似固定的時間間隔(由指定的週期分隔)進行後續執行。
    • 在固定速率執行中,根據已安排的初始執行時間來安排每次執行。若是因爲任何緣由(如垃圾回收或其餘後臺活動)而延遲了某次執行,則將快速連續地出現兩次或更多的執行,從而使後續執行可以「追遇上來」。從長遠來看,執行的頻率將正好是指定週期的倒數(假定 Object.wait(long) 所依靠的系統時鐘是準確的)。
    • 固定速率執行適用於那些對絕對時間敏感的重複執行活動,如每小時準點打鐘報時,或者在天天的特定時間運行已安排的維護活動。它還適用於那些完成固定次數執行的總計時間很重要的重複活動,如倒計時的計時器,每秒鐘滴答一次,共 10 秒鐘。最後,固定速率執行適用於安排多個重複執行的計時器任務,這些任務相互之間必須保持同步。
  • void scheduleAtFixedRate(TimerTask task, Date firstTime, long period) 安排指定的任務在指定的時間開始進行重複的固定速率執行。

TimerTask 計時任務

全部已實現的接口:Runnable
由 Timer 安排爲一次執行或重複執行的任務。oop

構造方法
protected TimerTask() 建立一個新的計時器任務。動畫

公共方法線程

  • boolean cancel() 取消此計時器任務。
    • 若是任務安排爲一次執行且還未運行,或者還沒有安排,則永遠不會運行。若是任務安排爲重複執行,則永遠不會再運行。(若是發生此調用時任務正在運行,則任務將運行完,但永遠不會再運行。)
    • 注意,從重複的計時器任務的 run 方法中調用此方法絕對保證計時器任務不會再運行。
    • 此方法能夠反覆調用;第二次和之後的調用無效。
    • 返回:若是此任務安排爲一次執行且還沒有運行,或者此任務安排爲重複執行,則返回 true。若是此任務安排爲一次執行且已經運行,或者此任務還沒有安排,或者此任務已經取消,則返回 false。(通常來講,若是此方法不容許發生一個或多個已安排執行,則返回 true。)
  • abstract void run() 此計時器任務要執行的操做。
  • long scheduledExecutionTime() 返回此任務最近實際執行的已安排執行時間。

案例

案例1:延時執行指定任務

final SimpleDateFormat format = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss SSS", Locale.getDefault());
System.out.println(format.format(new Date()));
new Timer().schedule(new TimerTask() {
    @Override
    public void run() {
        System.out.println(format.format(new Date()));
    }
}, 500);

案例2:執行定時任務

每10毫秒執行一次指定任務
一、使用 schedule 方式:

final SimpleDateFormat format = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss SSS", Locale.getDefault());
System.out.println("schedule 開始時間 " + format.format(new Date()));
new Timer().schedule(new TimerTask() {//或用 scheduleAtFixedRate 方法
    @Override
    public void run() {
        System.out.println("run  循環執行時間 " + format.format(new Date()));
    }
}, 0, 10);

打印結果:

schedule 開始時間 2018.06.12 09:27:31 186
run  循環執行時間 2018.06.12 09:27:31 189
run  循環執行時間 2018.06.12 09:27:31 200
run  循環執行時間 2018.06.12 09:27:31 211
run  循環執行時間 2018.06.12 09:27:31 222
run  循環執行時間 2018.06.12 09:27:31 233
run  循環執行時間 2018.06.12 09:27:31 243
run  循環執行時間 2018.06.12 09:27:31 254
run  循環執行時間 2018.06.12 09:27:31 265
run  循環執行時間 2018.06.12 09:27:31 276
run  循環執行時間 2018.06.12 09:27:31 287
run  循環執行時間 2018.06.12 09:27:31 297
run  循環執行時間 2018.06.12 09:27:31 308
run  循環執行時間 2018.06.12 09:27:31 318
run  循環執行時間 2018.06.12 09:27:31 329

能夠發現,後續執行時間和首次執行時間的偏差是愈來愈大的,若是時間很是長的話,偏差會很是明顯,因此schedule適合短期的週期任務。

二、使用 scheduleAtFixedRate 方式:
打印結果:

schedule 開始時間 2018.06.12 09:29:53 681
run  循環執行時間 2018.06.12 09:29:53 683
run  循環執行時間 2018.06.12 09:29:53 694
run  循環執行時間 2018.06.12 09:29:53 704
run  循環執行時間 2018.06.12 09:29:53 714
run  循環執行時間 2018.06.12 09:29:53 724
run  循環執行時間 2018.06.12 09:29:53 734
run  循環執行時間 2018.06.12 09:29:53 744
run  循環執行時間 2018.06.12 09:29:53 754
run  循環執行時間 2018.06.12 09:29:53 764
run  循環執行時間 2018.06.12 09:29:53 775
run  循環執行時間 2018.06.12 09:29:53 784
run  循環執行時間 2018.06.12 09:29:53 794
run  循環執行時間 2018.06.12 09:29:53 804

能夠發現,後續執行時間和首次執行時間的偏差基本是不變的,即時間很是長時偏差也很小,因此schedule適合長短期的週期任務。

Timer 的缺陷

Timer 拋出異常缺陷

Timer線程是不會捕獲異常的,若是TimerTask拋出的了未檢查異常則會致使Timer線程終止,同時Timer也不會從新恢復線程的執行,他會錯誤的認爲整個Timer線程都會取消。同時,已經被安排單還沒有執行的TimerTask也不會再執行了,新的任務也不能被調度。故若是TimerTask拋出未檢查的異常,Timer將會產生沒法預料的行爲(固然,能夠手動 try catch 捕獲此異常)。

下例中TimerTask拋出了未檢查的RuntimeException,以後Timer會終止全部任務的運行,該Timer線程也會被當即終止:

Timer timer = new Timer();
timer.schedule(new TimerTask() {
    @Override
    public void run() {
        System.out.println("執行第一個TimerTask  " + System.currentTimeMillis());
        throw new RuntimeException();//這裏拋出未檢測的RuntimeException以後,整個Timer線程會被終止
    }
}, 0);
​
Thread.sleep(100);
​
System.out.println("準備執行第二個TimerTask  " + System.currentTimeMillis());
timer.schedule(new TimerTask() {
    @Override
    public void run() {
        System.out.println("執行第二個TimerTask  " + System.currentTimeMillis());
    }
}, 0);

打印結果:

執行第一個TimerTask  1528768752890
Exception in thread "Timer-0" java.lang.RuntimeException
    at Test$1.run(Test.java:11)
    at java.util.TimerThread.mainLoop(Unknown Source)
    at java.util.TimerThread.run(Unknown Source)
準備執行第二個TimerTask  1528768752991
Exception in thread "main" java.lang.IllegalStateException: Timer already cancelled.
    at java.util.Timer.sched(Unknown Source)
    at java.util.Timer.schedule(Unknown Source)
    at Test.main(Test.java:18)

能夠發現,執行第一個TimerTask時出現未捕獲的異常後,第二個TimerTask就不會執行了,由於整個 Timer already cancelled 。

Timer 管理時間延遲缺陷

Timer在執行全部定時任務時只會建立一個線程,在執行週期性任務時,若是某個週期性任務的執行時間長度大於其週期時間長度,那麼就會致使這一次的任務還在執行,而下一個週期的任務已經須要開始執行了,這樣會致使愈來愈嚴重的時間延遲。

private int num = 0;
​
public void test()  {
    final SimpleDateFormat format = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss SSS", Locale.getDefault());
    new Timer().schedule(new TimerTask() { //換成 scheduleAtFixedRate 時效果基本一致
        @Override
        public void run() {
            System.out.println(num++ + "  " + format.format(new Date()));
            try {
                Thread.sleep(2000);//任務執行時間大於間隔時間
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }, 0, 1000);
}

打印結果:

0  2018.06.12 11:05:29 517
1  2018.06.12 11:05:31 517
2  2018.06.12 11:05:33 518
3  2018.06.12 11:05:35 519
4  2018.06.12 11:05:37 520
5  2018.06.12 11:05:39 520
6  2018.06.12 11:05:41 521
7  2018.06.12 11:05:43 522
8  2018.06.12 11:05:45 523
9  2018.06.12 11:05:47 524

一樣,若是存在多個任務,若其中某個任務執行時間過長, 會致使其餘的任務的實效準確性出現問題。

final SimpleDateFormat format = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss SSS", Locale.getDefault());
Timer timer = new Timer();
timer.schedule(new TimerTask() {
    @Override
    public void run() {
        System.out.println("執行第一個任務  " + format.format(new Date()));
        try {
            Thread.sleep(2000);//第一個任務執行時間過長
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}, 0);
​
timer.schedule(new TimerTask() {
    @Override
    public void run() {
        System.out.println("執行第二個任務  " + format.format(new Date()));//致使第二個任務的執行時間也受到影響
    }
}, 0);

打印結果:

執行第一個任務  2018.06.12 11:14:18 352
執行第二個任務  2018.06.12 11:14:20 353

用 ScheduledExecutorService 替代 Timer

能夠規避 Timer 拋出異常缺陷:

ScheduledExecutorService scheduExec = Executors.newScheduledThreadPool(2);
​
scheduExec.schedule(() -> {
    System.out.println("執行第一個任務  " + System.currentTimeMillis());
    throw new RuntimeException();//這裏拋出未檢測的RuntimeException
}, 0, TimeUnit.MILLISECONDS);
​
Thread.sleep(100);
​
scheduExec.schedule(() -> {
    System.out.println("執行第二個任務  " + System.currentTimeMillis());//第二個任務不會受到影響
}, 0, TimeUnit.MILLISECONDS);

打印結果:

執行第一個TimerTask  1528771566751
執行第二個TimerTask  1528771566852

能夠規避 Timer 管理時間延遲缺陷:

final SimpleDateFormat format = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss SSS", Locale.getDefault());
ScheduledExecutorService scheduExec = Executors.newScheduledThreadPool(2);
​
scheduExec.schedule(() -> {
    System.out.println("執行第一個任務  " + format.format(new Date()));
    try {
        Thread.sleep(2000);//第一個任務執行時間過長
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}, 0, TimeUnit.MILLISECONDS);
​
scheduExec.schedule(() -> {
    System.out.println("執行第二個任務  " + format.format(new Date()));//不會影響第二個任務的執行時間
}, 0, TimeUnit.MILLISECONDS);

打印結果:

執行第一個任務  2018.06.12 11:34:49 406
執行第二個任務  2018.06.12 11:34:49 406

注意,上面使用 Timer 執行週期性任務時有說到:若是某個週期性任務的執行時間長度大於其週期時間長度,那麼就會致使這一次的任務還在執行,而下一個週期的任務已經須要開始執行了,這樣會致使愈來愈嚴重的時間延遲。
這個問題對於 ScheduledExecutorService 一樣存在,JDK文檔中是這麼說的:對於 scheduleAtFixedRate ,若是此任務的任何一個執行要花費比其週期更長的時間,則將推遲後續執行,但不會同時執行。

2018-5-30

相關文章
相關標籤/搜索