Java定時(shí)任務(wù):利用java Timer類實(shí)現(xiàn)定時(shí)執(zhí)行任務(wù)的功能
一、概述
在java中實(shí)現(xiàn)定時(shí)執(zhí)行任務(wù)的功能,主要用到兩個(gè)類,Timer和TimerTask類。其中Timer是用來在一個(gè)后臺(tái)線程按指定的計(jì)劃來執(zhí)行指定的任務(wù)。
TimerTask一個(gè)抽象類,它的子類代表一個(gè)可以被Timer計(jì)劃的任務(wù),具體要執(zhí)行的代碼寫在TimerTask需要被實(shí)現(xiàn)的run方法中。
二、先看一個(gè)最簡單的例子
我們通過代碼來說明
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); } }
為了便于通過打印觀察信息,我們?cè)趍ain方法中加了些打印信息,并調(diào)用Thread.sleep讓主線程休眠一下。另外在類中增加了一個(gè)獲取當(dāng)前日期的getCurrentTime方法。
上面的代碼,在startTimer方法中,先創(chuàng)建了一個(gè)TimerTask對(duì)象(將要被定時(shí)器執(zhí)行的任務(wù)),然后創(chuàng)建了一個(gè)Timer對(duì)象,然后調(diào)用Timer類的schedule方法。Timer類有多個(gè)帶不同參數(shù)的schedule方法。這里用到的是:
public void schedule(TimerTask task, long delay)
該方法的含義是,表示定時(shí)器將延遲delay(毫秒)時(shí)間后,執(zhí)行task任務(wù)。如果delay為負(fù)數(shù)或0,則任務(wù)會(huì)被立即進(jìn)行。而且是一次性的執(zhí)行任務(wù),后續(xù)不會(huì)重復(fù)(或定時(shí))執(zhí)行該任務(wù)。
對(duì)于Timer類,還提供一個(gè)同樣功能的方法,如下:
public void schedule(TimerTask task, Date time)
該方法與上面方法的區(qū)別是,上面方法是指定延期一段時(shí)間執(zhí)行,這個(gè)方法是指定在某個(gè)具體的時(shí)間點(diǎn)執(zhí)行。注意,如果系統(tǒng)的當(dāng)前時(shí)間已經(jīng)超過了參數(shù)time指定的時(shí)間,該任務(wù)會(huì)被立即執(zhí)行。
當(dāng)運(yùn)行上面代碼時(shí),我們發(fā)現(xiàn)程序立即打印類似如下的2條信息:
main start:2016-01-13 22:23:18
task run:2016-01-13 22:23:18
因?yàn)槲覀冞@里給schedule方法傳遞的delay參數(shù)值為0,所以任務(wù)會(huì)被立即執(zhí)行,所以兩個(gè)語句打印出來的時(shí)間是一樣的,這是應(yīng)該的。大家可以自己改變傳入的delay值來看輸出信息的變化。再過大約5秒(即sleep的時(shí)間)后,繼續(xù)打印了1條信息:
main end:2016-01-13 22:23:23
打印信息的時(shí)間與上面語句差了5秒,與sleep設(shè)置的一致,也是很合理的。
但我們會(huì)發(fā)現(xiàn)一個(gè)很有趣的現(xiàn)象,會(huì)發(fā)現(xiàn)該進(jìn)程不會(huì)退出,這時(shí)main主線程已經(jīng)結(jié)束了,這說明定時(shí)器把任務(wù)完成后,即使后面沒有待等待執(zhí)行的任務(wù)了,定時(shí)器中創(chuàng)建的后臺(tái)線程也不會(huì)立即退出。查看了相關(guān)的java doc文檔,解釋說定時(shí)器線程不會(huì)主動(dòng)退出,需要等待垃圾回收,但java的待垃圾回收是無法通過代碼自己控制的,而是由虛擬機(jī)控制的。
研究了下,發(fā)現(xiàn)在創(chuàng)建Timer對(duì)象,及執(zhí)行Timer timer = new Timer(); 語句時(shí),定時(shí)器線程就會(huì)被創(chuàng)建。也就是說即使上面代碼沒有timer.schedule(task, 0);這個(gè)語句,程序也不會(huì)退出。感覺這個(gè)挺不合理的。再次研究了下Timer類的源代碼,發(fā)現(xiàn)其還有一個(gè)帶布爾參數(shù)的構(gòu)造函數(shù):
public Timer(boolean isDaemon)
從參數(shù)名就可以看出,如果參數(shù)值為true時(shí),則Timer創(chuàng)建的定時(shí)器線程為守護(hù)線程。守護(hù)線程的含義是,當(dāng)java進(jìn)程中所有的工作線程都退出后,守護(hù)線程就自動(dòng)退出了。
這時(shí)我們只要把上面例子中的創(chuàng)建Timer對(duì)象的代碼改為:Timer timer = new Timer(true);
發(fā)現(xiàn)運(yùn)行程序后,等main線程(main線程不是守護(hù)線程,是工作線程)結(jié)束后,程序會(huì)退出,也就是說定時(shí)器線程也退出了,說明加上參數(shù)true后,創(chuàng)建的它是守護(hù)線程了。
但問題是,在真正的應(yīng)用場景中,有很多工作線程在運(yùn)行,程序不會(huì)隨便退出。那如果要想定時(shí)器能立即退出或關(guān)閉,該怎么辦呢?這個(gè)我們下面介紹。
三、定時(shí)器的退出
Timer類提供了一個(gè)cancel方法可以取消定時(shí)器。調(diào)用cancel方法會(huì)終止此計(jì)時(shí)器,丟棄所有當(dāng)前已安排的任務(wù)。這不會(huì)干擾當(dāng)前正在執(zhí)行的任務(wù)(如果存在)。一旦終止了計(jì)時(shí)器,那么它的執(zhí)行線程也會(huì)終止,并且無法根據(jù)它安排更多的任務(wù)。
注意,在此計(jì)時(shí)器調(diào)用的計(jì)時(shí)器任務(wù)的 run 方法內(nèi)調(diào)用此方法,就可以絕對(duì)確保正在執(zhí)行的任務(wù)是此計(jì)時(shí)器所執(zhí)行的最后一個(gè)任務(wù)??梢灾貜?fù)調(diào)用此方法;但是第二次和后續(xù)調(diào)用無效。
我們?cè)倏匆粋€(gè)例子代碼:
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; } }
運(yùn)行程序,跟上面一個(gè)例子的輸出情況完全一樣。區(qū)別是,當(dāng)main方法結(jié)束后。進(jìn)程會(huì)主動(dòng)退出,也就是說定時(shí)器線程已經(jīng)關(guān)閉了。
因?yàn)槲覀冊(cè)趍ain方法中調(diào)用了cancel方法。 注意,如果不是在TimerTask的run方法中調(diào)用cancel方法一定要注意,一定要確保希望執(zhí)行的任務(wù)已經(jīng)開始執(zhí)行或執(zhí)行完畢,否則如果任務(wù)還未開始執(zhí)行。就調(diào)用cancel,則所有任務(wù)都不會(huì)被執(zhí)行了。比如上面的代碼,
比如上面的代碼,如果我們不在main方法中調(diào)用cancel方法,而是在startTimer方法中 timer.schedule(task, 0); 語句后加上timer.cancel();語句,運(yùn)行后會(huì)發(fā)現(xiàn),定時(shí)器任務(wù)不會(huì)被執(zhí)行,因?yàn)檫€未來得及執(zhí)行就被取消中止了。
四、定時(shí)執(zhí)行任務(wù)
上面的例子,我們介紹的是一次性任務(wù),也就是定時(shí)器時(shí)間到了,執(zhí)行完任務(wù),后面不會(huì)再重復(fù)執(zhí)行。在實(shí)際的應(yīng)用中,有很多場景需要定時(shí)重復(fù)的執(zhí)行同一個(gè)任務(wù)。這也分兩種情況,一是每隔一段時(shí)間就執(zhí)行任務(wù),二是每天(或每周、每月等)的固定某個(gè)(或某幾個(gè))時(shí)間點(diǎn)來執(zhí)行任務(wù)。
我們先來看第一種情況,實(shí)現(xiàn)每隔10秒執(zhí)行同一任務(wù)的例子。代碼如下:
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); } }
執(zhí)行上述程序,輸出信息如下(因?yàn)槎〞r(shí)器沒有停止,重復(fù)執(zhí)行任務(wù),會(huì)不斷輸出,這里只拷貝了前面的一些輸出)
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
在上面的代碼中,我們調(diào)用了 timer.schedule(task, 1000*5,1000*10); 這個(gè)含義是該任務(wù)延遲5秒后執(zhí)行,然后會(huì)每隔10秒重復(fù)執(zhí)行。我們觀察輸出信息中打印的時(shí)間,是與預(yù)期一樣的。 另外可以看出,間隔是以任務(wù)開始執(zhí)行時(shí)間為起點(diǎn)算的,也就是并不是任務(wù)執(zhí)行完成后再等待10秒。
Timer類有兩個(gè)方法可以實(shí)現(xiàn)這樣的功能,如下:
public void schedule(TimerTask task, long delay, long period) public void schedule(TimerTask task, Date firstTime, long period)
我們上面代碼用的是第一個(gè)方法。兩個(gè)方法區(qū)別在于第一次執(zhí)行的時(shí)間,第一個(gè)方法是在指定延期一段時(shí)間(單位為毫秒)后執(zhí)行;第二個(gè)方法是在指定的時(shí)間點(diǎn)執(zhí)行。
這時(shí)我們考慮如下場景,如果某個(gè)任務(wù)的執(zhí)行耗時(shí)超過了下次等待時(shí)間,會(huì)出現(xiàn)什么情況呢? 我們還是通過代碼來看:
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秒,二是將任務(wù)的執(zhí)行周期改為5秒。也就說任務(wù)的執(zhí)行耗時(shí)超過了任務(wù)重復(fù)執(zhí)行的間隔。運(yùn)行程序,前面的輸出如下:
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
可以看出,每個(gè)任務(wù)執(zhí)行完成后,會(huì)立即執(zhí)行下一個(gè)任務(wù)。因?yàn)閺娜蝿?wù)開始執(zhí)行到任務(wù)完成的耗時(shí)已經(jīng)超過了任務(wù)重復(fù)的間隔時(shí)間,所以會(huì)重復(fù)執(zhí)行。
五、定時(shí)執(zhí)行任務(wù)(重復(fù)固定時(shí)間點(diǎn)執(zhí)行)
我們來實(shí)現(xiàn)這樣一個(gè)功能,每天的凌晨1點(diǎn)定時(shí)執(zhí)行一個(gè)任務(wù),這在很多系統(tǒng)中都有這種功能,比如在這個(gè)任務(wù)中完成數(shù)據(jù)備份、數(shù)據(jù)統(tǒng)計(jì)等耗時(shí)、耗資源較多的任務(wù)。代碼如下:
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())) { //若果當(dāng)前時(shí)間已經(jīng)是凌晨1點(diǎn)后,需要往后加1天,否則任務(wù)會(huì)立即執(zhí)行。 //很多系統(tǒng)往往系統(tǒng)啟動(dòng)時(shí)就需要立即執(zhí)行一次任務(wù),但下面又需要每天凌晨1點(diǎn)執(zhí)行,怎么辦呢? //很簡單,就在系統(tǒng)初始化話時(shí)單獨(dú)執(zhí)行一次任務(wù)(不需要用定時(shí)器,只是執(zhí)行那段任務(wù)的代碼) 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(); } }
因?yàn)槭情g隔24小時(shí)執(zhí)行,沒法等待觀察輸出。
六、小結(jié)
本文介紹了利用java Timer類如何執(zhí)行定時(shí)任務(wù)的機(jī)制??梢钥闯觯€是有許多需要注意的方法。 本文中介紹的例子,每個(gè)定時(shí)器只對(duì)應(yīng)一個(gè)任務(wù)。
本文介紹的內(nèi)容可以滿足大部分應(yīng)用場景了,但還有一些問題,比如對(duì)于一個(gè)定時(shí)器包括多個(gè)任務(wù)?定時(shí)器取消后能否再次添加任務(wù)?Timer類中還有哪些方法可用? 這些問題,我們?cè)俸竺娴牟┪闹薪榻B。
原文鏈接:http://www.cnblogs.com/51kata/p/5128745.html
以上就是本文的全部內(nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Java定時(shí)任務(wù)的三種實(shí)現(xiàn)方法
- Java定時(shí)任務(wù)的三種實(shí)現(xiàn)方式
- Quartz實(shí)現(xiàn)JAVA定時(shí)任務(wù)的動(dòng)態(tài)配置的方法
- Java實(shí)現(xiàn)終止線程池中正在運(yùn)行的定時(shí)任務(wù)
- java定時(shí)任務(wù)Timer和TimerTask使用詳解
- Java 實(shí)現(xiàn)定時(shí)任務(wù)的三種方法
- 最流行的java后臺(tái)框架spring quartz定時(shí)任務(wù)
- java中 spring 定時(shí)任務(wù) 實(shí)現(xiàn)代碼
- 在Java Web項(xiàng)目中添加定時(shí)任務(wù)的方法
- java實(shí)現(xiàn)周期性執(zhí)行(定時(shí)任務(wù))
相關(guān)文章
Java中if...else語句使用的學(xué)習(xí)教程
這篇文章主要介紹了Java中if...else語句使用的學(xué)習(xí)教程,是Java入門學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2015-11-11mybatis判斷int是否為空的時(shí)候,需要注意的3點(diǎn)
這篇文章主要介紹了mybatis判斷int是否為空的時(shí)候,需要注意的3點(diǎn),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07淺談spring-boot 允許接口跨域并實(shí)現(xiàn)攔截(CORS)
本篇文章主要介紹了淺談spring-boot 允許接口跨域并實(shí)現(xiàn)攔截(CORS),具有一定的參考價(jià)值,有興趣的可以了解一下2017-08-08java多線程編程之Synchronized關(guān)鍵字詳解
這篇文章主要為大家詳細(xì)介紹了java多線程編程之Synchronized關(guān)鍵字,感興趣的朋友可以參考一下2016-05-05Feign調(diào)用中的兩種Header傳參方式小結(jié)
這篇文章主要介紹了Feign調(diào)用中的兩種Header傳參方式小結(jié),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-01-01AbstractQueuedSynchronizer(AQS)鎖狀態(tài)同步和排隊(duì)管理
這篇文章主要介紹了為大家AbstractQueuedSynchronizer(AQS)鎖狀態(tài)同步和排隊(duì)管理源碼解讀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-11-11