1. 程式人生 > >Java定時任務:利用java Timer類實現定時執行任務的功能

Java定時任務:利用java Timer類實現定時執行任務的功能

lpad 虛擬 觀察 exce 就是 set ring 構造 trac

一、概述

在java中實現定時執行任務的功能,主要用到兩個類,Timer和TimerTask類。其中Timer是用來在一個後臺線程按指定的計劃來執行指定的任務。

TimerTask一個抽象類,它的子類代表一個可以被Timer計劃的任務,具體要執行的代碼寫在TimerTask需要被實現的run方法中。

二、先看一個最簡單的例子

我們通過代碼來說明

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 import java.text.SimpleDateFormat; import
java.util.Date; import java.util.Timer; import java.util.TimerTask; public class TimerDemo { public static String getCurrentTime() { Date date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); return sdf.format(date); } public static void main(String[] args)
throws InterruptedException { System.out.println("main start:"+getCurrentTime()); startTimer(); Thread.sleep(1000*5); //休眠5秒 System.out.println("main end:"+getCurrentTime()); } public static void startTimer(){ TimerTask task = new TimerTask() { @Override public void
run() { System.out.println("task run:"+getCurrentTime()); } }; Timer timer = new Timer(); timer.schedule(task, 0); } }

為了便於通過打印觀察信息,我們在main方法中加了些打印信息,並調用Thread.sleep讓主線程休眠一下。另外在類中增加了一個獲取當前日期的getCurrentTime方法。

上面的代碼,在startTimer方法中,先創建了一個TimerTask對象(將要被定時器執行的任務),然後創建了一個Timer對象,然後調用Timer類的schedule方法。Timer類有多個帶不同參數的schedule方法。這裏用到的是: 

?
1 public void schedule(TimerTask task, long delay)

該方法的含義是,表示定時器將延遲delay(毫秒)時間後,執行task任務。如果delay為負數或0,則任務會被立即進行。而且是一次性的執行任務,後續不會重復(或定時)執行該任務。

對於Timer類,還提供一個同樣功能的方法,如下: 

?
1 public void schedule(TimerTask task, Date time)

該方法與上面方法的區別是,上面方法是指定延期一段時間執行,這個方法是指定在某個具體的時間點執行。註意,如果系統的當前時間已經超過了參數time指定的時間,該任務會被立即執行。

當運行上面代碼時,我們發現程序立即打印類似如下的2條信息:

main start:2016-01-13 22:23:18
task run:2016-01-13 22:23:18

因為我們這裏給schedule方法傳遞的delay參數值為0,所以任務會被立即執行,所以兩個語句打印出來的時間是一樣的,這是應該的。大家可以自己改變傳入的delay值來看輸出信息的變化。再過大約5秒(即sleep的時間)後,繼續打印了1條信息:

main end:2016-01-13 22:23:23

打印信息的時間與上面語句差了5秒,與sleep設置的一致,也是很合理的。

但我們會發現一個很有趣的現象,會發現該進程不會退出,這時main主線程已經結束了,這說明定時器把任務完成後,即使後面沒有待等待執行的任務了,定時器中創建的後臺線程也不會立即退出。查看了相關的java doc文檔,解釋說定時器線程不會主動退出,需要等待垃圾回收,但java的待垃圾回收是無法通過代碼自己控制的,而是由虛擬機控制的。

研究了下,發現在創建Timer對象,及執行Timer timer = new Timer(); 語句時,定時器線程就會被創建。也就是說即使上面代碼沒有timer.schedule(task, 0);這個語句,程序也不會退出。感覺這個挺不合理的。再次研究了下Timer類的源代碼,發現其還有一個帶布爾參數的構造函數:

?
1 public Timer(boolean isDaemon)

從參數名就可以看出,如果參數值為true時,則Timer創建的定時器線程為守護線程。守護線程的含義是,當java進程中所有的工作線程都退出後,守護線程就自動退出了。

這時我們只要把上面例子中的創建Timer對象的代碼改為:Timer timer = new Timer(true);

發現運行程序後,等main線程(main線程不是守護線程,是工作線程)結束後,程序會退出,也就是說定時器線程也退出了,說明加上參數true後,創建的它是守護線程了。

但問題是,在真正的應用場景中,有很多工作線程在運行,程序不會隨便退出。那如果要想定時器能立即退出或關閉,該怎麽辦呢?這個我們下面介紹。

三、定時器的退出

Timer類提供了一個cancel方法可以取消定時器。調用cancel方法會終止此計時器,丟棄所有當前已安排的任務。這不會幹擾當前正在執行的任務(如果存在)。一旦終止了計時器,那麽它的執行線程也會終止,並且無法根據它安排更多的任務。

註意,在此計時器調用的計時器任務的 run 方法內調用此方法,就可以絕對確保正在執行的任務是此計時器所執行的最後一個任務。可以重復調用此方法;但是第二次和後續調用無效。

我們再看一個例子代碼:

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import java.text.SimpleDateFormat; import java.util.Date; import java.util.Timer; import java.util.TimerTask; public class TimerDemo { public static String getCurrentTime() { Date date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); return sdf.format(date); } public static void main(String[] args) throws InterruptedException { System.out.println("main start:"+getCurrentTime()); Timer timer = startTimer(); Thread.sleep(1000*5); //休眠5秒 System.out.println("main end:"+getCurrentTime()); timer.cancel(); } public static Timer startTimer(){ TimerTask task = new TimerTask() { @Override public void run() { System.out.println("task run:"+getCurrentTime()); } }; Timer timer = new Timer(); timer.schedule(task, 0); return timer; } }

運行程序,跟上面一個例子的輸出情況完全一樣。區別是,當main方法結束後。進程會主動退出,也就是說定時器線程已經關閉了。

因為我們在main方法中調用了cancel方法。 註意,如果不是在TimerTask的run方法中調用cancel方法一定要註意,一定要確保希望執行的任務已經開始執行或執行完畢,否則如果任務還未開始執行。就調用cancel,則所有任務都不會被執行了。比如上面的代碼,

比如上面的代碼,如果我們不在main方法中調用cancel方法,而是在startTimer方法中 timer.schedule(task, 0); 語句後加上timer.cancel();語句,運行後會發現,定時器任務不會被執行,因為還未來得及執行就被取消中止了。

四、定時執行任務

上面的例子,我們介紹的是一次性任務,也就是定時器時間到了,執行完任務,後面不會再重復執行。在實際的應用中,有很多場景需要定時重復的執行同一個任務。這也分兩種情況,一是每隔一段時間就執行任務,二是每天(或每周、每月等)的固定某個(或某幾個)時間點來執行任務。

我們先來看第一種情況,實現每隔10秒執行同一任務的例子。代碼如下:

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 import java.text.SimpleDateFormat; import java.util.Date; import java.util.Timer; import java.util.TimerTask; public class TimerDemo { public static String getCurrentTime() { Date date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); return sdf.format(date); } public static void main(String[] args) throws InterruptedException { System.out.println("main start:"+getCurrentTime()); startTimer(); } public static void startTimer(){ TimerTask task = new TimerTask() { @Override public void run() { System.out.println("task run:"+getCurrentTime()); try { Thread.sleep(1000*3); } catch (InterruptedException e) { e.printStackTrace(); } } }; Timer timer = new Timer(); timer.schedule(task, 1000*5,1000*10); } }

執行上述程序,輸出信息如下(因為定時器沒有停止,重復執行任務,會不斷輸出,這裏只拷貝了前面的一些輸出)

main start:2016-01-14 08:41:14
task run:2016-01-14 08:41:19
task run:2016-01-14 08:41:29
task run:2016-01-14 08:41:39
task run:2016-01-14 08:41:49
task run:2016-01-14 08:42:00
task run:2016-01-14 08:42:10
task run:2016-01-14 08:42:20
task run:2016-01-14 08:42:30
task run:2016-01-14 08:42:40

在上面的代碼中,我們調用了 timer.schedule(task, 1000*5,1000*10); 這個含義是該任務延遲5秒後執行,然後會每隔10秒重復執行。我們觀察輸出信息中打印的時間,是與預期一樣的。 另外可以看出,間隔是以任務開始執行時間為起點算的,也就是並不是任務執行完成後再等待10秒。

Timer類有兩個方法可以實現這樣的功能,如下:

?
1 2 3 public void schedule(TimerTask task, long delay, long period) public void schedule(TimerTask task, Date firstTime, long period)

我們上面代碼用的是第一個方法。兩個方法區別在於第一次執行的時間,第一個方法是在指定延期一段時間(單位為毫秒)後執行;第二個方法是在指定的時間點執行。

這時我們考慮如下場景,如果某個任務的執行耗時超過了下次等待時間,會出現什麽情況呢? 我們還是通過代碼來看:

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 import java.text.SimpleDateFormat; import java.util.Date; import java.util.Timer; import java.util.TimerTask; public class TimerDemo { public static String getCurrentTime() { Date date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); return sdf.format(date); } public static void main(String[] args) throws InterruptedException { System.out.println("main start:"+getCurrentTime()); startTimer(); } public static void startTimer(){ TimerTask task = new TimerTask() { @Override public void run() { System.out.println("task begin:"+getCurrentTime()); try { Thread.sleep(1000*10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("task end:"+getCurrentTime()); } }; Timer timer = new Timer(); timer.schedule(task, 1000*5,1000*5); } }

與前面代碼相比,我們只改了2處代碼和修改了下打印,一是將run方法中的sleep改為了10秒,二是將任務的執行周期改為5秒。也就說任務的執行耗時超過了任務重復執行的間隔。運行程序,前面的輸出如下:

main start:2016-01-14 09:03:51
task begin:2016-01-14 09:03:56
task end:2016-01-14 09:04:06
task begin:2016-01-14 09:04:06
task end:2016-01-14 09:04:16
task begin:2016-01-14 09:04:16
task end:2016-01-14 09:04:26
task begin:2016-01-14 09:04:26
task end:2016-01-14 09:04:36
task begin:2016-01-14 09:04:36
task end:2016-01-14 09:04:46
task begin:2016-01-14 09:04:46
task end:2016-01-14 09:04:56

可以看出,每個任務執行完成後,會立即執行下一個任務。因為從任務開始執行到任務完成的耗時已經超過了任務重復的間隔時間,所以會重復執行。

五、定時執行任務(重復固定時間點執行)

我們來實現這樣一個功能,每天的淩晨1點定時執行一個任務,這在很多系統中都有這種功能,比如在這個任務中完成數據備份、數據統計等耗時、耗資源較多的任務。代碼如下:

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.Timer; import java.util.TimerTask; public class TimerDemo { public static String getCurrentTime() { Date date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); return sdf.format(date); } public static void main(String[] args) throws InterruptedException { System.out.println("main start:" + getCurrentTime()); startTimer(); } public static void startTimer() { TimerTask task = new TimerTask() { @Override public void run() { System.out.println("task begin:" + getCurrentTime()); try { Thread.sleep(1000 * 20); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("task end:" + getCurrentTime()); } }; Timer timer = new Timer(); timer.schedule(task, buildTime(), 1000 * 60 * 60 * 24); } private static Date buildTime() { Calendar calendar = Calendar.getInstance(); calendar.set(Calendar.HOUR_OF_DAY, 1); calendar.set(Calendar.MINUTE, 0); calendar.set(Calendar.SECOND, 0); Date time = calendar.getTime(); if (time.before(new Date())) { //若果當前時間已經是淩晨1點後,需要往後加1天,否則任務會立即執行。 //很多系統往往系統啟動時就需要立即執行一次任務,但下面又需要每天淩晨1點執行,怎麽辦呢? //很簡單,就在系統初始化話時單獨執行一次任務(不需要用定時器,只是執行那段任務的代碼) time = addDay(time, 1); } return time; } private static Date addDay(Date date, int days) { Calendar startDT = Calendar.getInstance(); startDT.setTime(date); startDT.add(Calendar.DAY_OF_MONTH, days); return startDT.getTime(); } }

因為是間隔24小時執行,沒法等待觀察輸出。

六、小結

本文介紹了利用java Timer類如何執行定時任務的機制。可以看出,還是有許多需要註意的方法。 本文中介紹的例子,每個定時器只對應一個任務。

本文介紹的內容可以滿足大部分應用場景了,但還有一些問題,比如對於一個定時器包括多個任務?定時器取消後能否再次添加任務?Timer類中還有哪些方法可用? 這些問題,我們再後面的博文中介紹。

轉自 http://www.jb51.net/article/97382.htm

Java定時任務:利用java Timer類實現定時執行任務的功能