Java定時任務Timer、TimerTask與ScheduledThreadPoolExecutor詳解

   定時任務就是在指定時間執行程序,或週期性執行計劃任務。Java中實現定時任務的方法有不少,本文從從JDK自帶的一些方法來實現定時任務的需求。html

1、Timer和TimerTask

   Timer和TimerTask能夠做爲線程實現的第三種方式(前兩種詳見《Java多線程基礎》),JDK1.5以後定時任務推薦使用ScheduledThreadPoolExecutorjava

一、快速入門多線程

   Timer運行在後臺,能夠執行任務一次,或按期執行任務。TimerTask類繼承了Runnable接口,所以具有多線程的能力。一個Timer能夠調度任意多個TimerTask,全部任務都存儲在一個隊列中順序執行,若是須要多個TimerTask併發執行,則須要建立兩個多個Timer。併發

  一個簡單使用Timer的例子以下:ide

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

public class TimerTest {  
    //被執行的任務必須繼承TimerTask,而且實現run方法
    static class MyTimerTask1 extends TimerTask {  
        public void run() {  
            System.out.println("爆炸!!!");  
        }  
    }     
    public static void main(String[] args) throws ParseException {  
        Timer timer = new Timer();  
        //一、設定兩秒後執行任務
        //timer.scheduleAtFixedRate(new MyTimerTask1(), 2000,1000);
        //二、設定任務在執行時間執行,本例設定時間13:57:00
        SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");  
        Date time = dateFormatter.parse("2014/02/11 14:40:00");  
        timer.schedule(new MyTimerTask1(), time);
    }  
}  
View Code

二、schedule與scheduleAtFixedRate使用方法spa

   schedule(TimerTask task, long delay, long period)   --指定任務執行延遲時間線程

   schedule(TimerTask task, Date time, long period)    --指定任務執行時刻code

   scheduleAtFixedRate(TimerTask task, long delay, long period)orm

   scheduleAtFixedRate(TimerTask task, Date firstTime, long period)htm

三、schedule與scheduleAtFixedRate區別

   1) schedule:

   ① 注重任務執行的平滑度,也就是說任務隊列中某個任務執行延遲了某個時間,接下來的其他任務都會延遲相同時間,來最大限度的保證任務與任務之間的時間間隔的完整性;

   ② 當程序指定開始時刻(Date time)小於當前系統時刻時,會當即執行一次任務,以後的任務開始執行時間以當前時刻爲標準,結合時間間隔計算獲得;

   例:計劃任務程序指定從2014/02/11 18:00:00開始每隔3分鐘執行一次任務。若是該程序在18:00:00以前運行,則計劃任務程序分別會在18:00:00、18:03:00、18:06:00...等時間點執行任務;若是該程序在18:00:00以後運行,如在18:07:00時刻開始運行程序,計劃任務程序判斷指定開始執行時刻18:00:00小於當前系統時刻,因而當即執行一次任務,接下來任務時間時刻分別爲18:10:00、18:13:00、18:16:00...;而當使用scheduleAtFixedRate執行計劃任務時,不管計劃任務程序在何時運行,全部任務執行的次數都按照原計劃,不會由於程序執行時刻的遲早而改變。而當程序運行時刻比計劃任務計劃首次執行時間晚時,如一樣在18:07:00時刻開始執行程序,則計劃任務程序會立馬計算程序執行時刻晚於指定時刻,會當即執行(18:07:00-18:00:00)/3+1=3次任務(表明18:00:00、18:03:00和18:06:00三個時刻執行的任務),接下來任務執行時刻是18:09:00、18:12:00等。

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;


public class TimerRateFix {
    public static void main(String[] args) throws ParseException {
        final SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");  
        Date startDate = dateFormatter.parse("2014/02/11 18:00:00");  
        Timer timer = new Timer();  
        timer.schedule(new TimerTask(){  
           public void run()  
           {  
               System.out.println("執行任務,當前時刻:" + dateFormatter.format(new Date()));  
           }  
        },startDate,3*60*1000);  
    }
}
View Code

   ③ 當執行任務的時間間隔t1大於週期間隔t2時,下一次任務執行時間點相對於上一次任務實際執行完成的時間點,每一個任務的執行時間會延後,第n個計劃任務的實際執行時間比預計要延後(t1-t2)*n個時間單位。

   例:計劃任務程序指定從2014/02/11 18:00:00開始每隔5秒執行一次任務,每次任務執行時間爲6秒。當程序在18:00:00以前執行時,schedule分別會在18:00:00、18:00:0六、18:00:12...等時間點執行計劃任務,每隔時間點間隔6秒。緣由是根據計劃,第一個計劃任務應會在18:00:00執行,第二個計劃任務應會在18:00:05執行,而在18:00:05時間點,第一個任務才執行了5秒,還須要1秒才執行結束,所以第二個任務不能執行,因而等待1秒後在18:00:06時刻執行,以後每一個任務均如此,均比原定執行時刻有延遲,每一個任務時間間隔爲6秒。當使用scheduleAtFixedRate執行計劃任務時,第一個計劃任務在18:00:00時刻執行,第二個會根據計劃在18:00:05執行,第三個會在18:00:10執行,每一個任務執行時間間隔爲5秒,詳細執行狀況以下圖所示

圖1 schedule與scheduleAtFixedRate任務執行區別

import java.text.ParseException;  
import java.text.SimpleDateFormat;  
import java.util.Date;  
import java.util.Timer;  
import java.util.TimerTask;  
public class TimerRateTest {  
    public static void main(String[] args) throws ParseException {  
        final SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");  
        Timer timer = new Timer();  
        Date time = dateFormatter.parse("2014/02/11 18:00:00");
        //假設程序在2014/02/11 18:00:00以前啓動
        //一、使用scheduleAtFixedRate,每一個計劃任務執行時間點嚴格爲18:00:00、18:00:0五、18:00:10...,當任務執行時間大於時間間隔時可能會有併發狀況
        //二、使用schedule,每一個計劃任務執行時間點根據上一個任務執行結束時間及時間間隔來計算
        //     當任務執行時間t1>時間間隔t2時,第N個計劃任務執行時間點延遲爲(t1-t2)*N,執行時間點爲18:00:00+t2*(N-1)+(t1-t2)*N
        //    當任務執行時間t1<=時間間隔t2時,第N個計劃任務執行時間點無延遲,執行時間爲原計劃
        timer.scheduleAtFixedRate(new TimerTask(){  
           public void run() {  
               try {  
                   //每一個計劃任務執行時間爲6秒
                   Thread.sleep(6000);  
               } catch (InterruptedException e) {  
                   e.printStackTrace();  
               }
               System.out.println("結束當前任務,當前時間:"+ dateFormatter.format(new Date()));  
           }  
        },time,5000);  //計劃任務執行時間間隔爲5秒
    }  
}  
View Code

   2) scheduleAtFixedRate:

   ① 注重任務執行的頻度,也就是說計劃任務程序開始執行,每隔任務執行的時間點就已經肯定,並不會由於某個任務的延遲而延遲執行其餘任務,能夠保證任務執行的時間效率;

   ② 當程序指定開始時刻(Date firstTime)小於當前系統時刻時,會當即執行任務,執行次數爲(當前系統時刻-指定開始時刻)/時間間隔,以後的任務開始執行時刻與當前系統時刻無關,仍按照程序指定開始時刻根據時間間隔計算獲得;

   ③ 當執行任務的時間間隔t1大於週期間隔t2時,下一次任務執行時間點仍是按照原定計劃不變,所以這種狀況,有部分時間斷可能有多個任務併發執行

四、終止Timer線程

   1) 調用Timer.cancle()方法。能夠在程序任何地方調用,甚至在TimerTask中的run方法中調用;

   2) 建立Timer時定義位daemon守護線程(有關守護線程見《Java守護線程》),使用new Timer(true)語句;

   3) 設置Timer對象爲null,其會自動終止;

   4) 調用System.exit方法,整個程序終止。

五、Timer線程的缺點

   1) Timer線程不會捕獲異常,因此TimerTask拋出的未檢查的異常會終止timer線程。若是Timer線程中存在多個計劃任務,其中一個計劃任務拋出未檢查的異常,則會引發整個Timer線程結束,從而致使其餘計劃任務沒法獲得繼續執行。  

   2) Timer線程時基於絕對時間(如:2014/02/14 16:06:00),所以計劃任務對系統的時間的改變是敏感的。

   3) Timer是單線程,若是某個任務很耗時,可能會影響其餘計劃任務的執行。

   所以,JDK1.5以上建議使用ScheduledThreadPoolExecutor來代替Timer執行計劃任務。   

2、ScheduledThreadPoolExecutor

  ScheduledThreadPoolExecutor是JDK1.5之後推出的類,用於實現定時、重複執行的功能,官方文檔解釋要優於Timer。

一、構造方法   

   1) ScheduledThreadPoolExecutor(int corePoolSize) 使用給定核心池大小建立一個新定定時線程池 

   2) ScheduledThreadPoolExecutor(int corePoolSize, ThreadFactorythreadFactory) 使用給定的初始參數建立一個新對象,可提供線程建立工廠

private final static ScheduledThreadPoolExecutor schedual = new ScheduledThreadPoolExecutor(1, new ThreadFactory() {
        private AtomicInteger atoInteger = new AtomicInteger(0);
        public Thread newThread(Runnable r) {
            Thread t = new Thread(r);
            t.setName("xxx-Thread "+ atoInteger.getAndIncrement());
            return t;
        }
});
View Code

二、調度方法

   1) schedule(Callable callable, long delay, TimeUnit unit);  延遲delay時間後開始執行callable

   2) scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit);  延遲initialDelay時間後開始執行command,而且按照period時間週期性重複調用,當任務執行時間大於間隔時間時,以後的任務都會延遲,此時與Timer中的schedule方法相似

   3) scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit);  延遲initialDelay時間後開始執行command,而且按照period時間週期性重複調用,這裏的間隔時間delay是等上一個任務徹底執行完畢纔開始計算,與Timer中scheduleAtFixedRate狀況不一樣。

圖2 ScheduledThreadPoolExecutor.scheduleWithFixedDelay與Timer.scheduleAtFixedRate任務執行區別

三、與Timer相比,優勢有:

   1) ScheduledThreadPoolExecutor線程會捕獲任務重的異常,即便多個計劃任務中存在某幾個計劃任務爲捕獲異常的狀況,也不會影響ScheduledThreadPoolExecutor總線程的工做,不會影響其餘計劃任務的繼續執行。

   2) ScheduledThreadPoolExecutor是基於相對時間的,對系統時間的改變不敏感,可是若是執行某一絕對時間(如2014/02/14 17:13:06)執行任務,可能很差執行,此時可以使用Timer。

   3) ScheduledThreadPoolExecutor是線程池,如任務數過多或某些任務執行時間較長,可自動分配更多的線程來執行計劃任務。

   總之,JDK1.5以後,計劃任務建議使用ScheduledThreadPoolExecutor。

相關文章
相關標籤/搜索