Markdown版本筆記 | 個人GitHub首頁 | 個人博客 | 個人微信 | 個人郵箱 |
---|---|---|---|---|
MyAndroidBlogs | baiqiantao | baiqiantao | bqt20094 | baiqiantao@sina.com |
線程 Timer TimerTask 計時器 定時任務java
Timer能夠定時(在指定時間執行任務)、延遲(延遲指定時間執行任務)、週期性地執行任務(每隔指定時間執行一次任務)。git
一種工具,線程用其安排之後在後臺線程中執行的任務。可安排任務執行一次,或者按期重複執行。
與每一個 Timer 對象相對應的是單個後臺線程,用於順序地執行全部計時器任務。計時器任務應該迅速完成。若是完成某個計時器任務的時間太長,那麼它會「獨佔」計時器的任務執行線程。所以,這就可能延遲後續任務的執行,而這些任務就可能「堆在一塊兒」,而且在上述不友好的任務最終完成時纔可以被快速連續地執行。github
對 Timer 對象最後的引用完成後,而且 全部未處理的任務都已執行完成後,計時器的任務執行線程會正常終止(而且成爲垃圾回收的對象)。可是這可能要很長時間後才發生。
默認狀況下,任務執行線程並不做爲守護線程來運行,因此它可以阻止應用程序終止。若是調用者想要快速終止計時器的任務執行線程,那麼調用者應該調用計時器的 cancel 方法。
若是意外終止了計時器的任務執行線程,例如調用了它的 stop 方法,那麼全部之後對該計時器安排任務的嘗試都將致使 IllegalStateException,就好像調用了計時器的 cancel 方法同樣。安全
此類是線程安全的:多個線程能夠共享單個 Timer 對象而無需進行外部同步。
此類不提供實時保證:它使用 Object.wait(long) 方法來安排任務。
實現注意事項:此類可擴展到大量同時安排的任務(存在數千個都沒有問題)。在內部,它使用二進制堆來表示其任務隊列,因此安排任務的開銷是 O(log n),其中 n 是同時安排的任務數。
實現注意事項:全部構造方法都啓動計時器線程。微信
構造方法ide
公共方法工具
全部已實現的接口:Runnable
由 Timer 安排爲一次執行或重複執行的任務。oop
構造方法
protected TimerTask() 建立一個新的計時器任務。動畫
公共方法線程
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);
每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線程是不會捕獲異常的,若是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在執行全部定時任務時只會建立一個線程,在執行週期性任務時,若是某個週期性任務的執行時間長度大於其週期時間長度,那麼就會致使這一次的任務還在執行,而下一個週期的任務已經須要開始執行了,這樣會致使愈來愈嚴重的時間延遲。
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 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
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