Java多線程定時(shí)器Timer原理及實(shí)現(xiàn)
前言
定時(shí)/計(jì)劃功能在Java應(yīng)用的各個(gè)領(lǐng)域都使用得非常多,比方說Web層面,可能一個(gè)項(xiàng)目要定時(shí)采集話單、定時(shí)更新某些緩存、定時(shí)清理一批不活躍用戶等等。定時(shí)計(jì)劃任務(wù)功能在Java中主要使用的就是Timer對(duì)象,它在內(nèi)部使用多線程方式進(jìn)行處理,所以它和多線程技術(shù)關(guān)聯(lián)還是相當(dāng)大的。那和ThreadLocal一樣,還是先講原理再講使用,Timer的實(shí)現(xiàn)原理不難,就簡單掃一下就好了。
Timer的schedule(TimeTask task, Date time)的使用
該方法的作用是在執(zhí)行的日期執(zhí)行一次任務(wù)
1、執(zhí)行任務(wù)的時(shí)間晚于當(dāng)前時(shí)間:未來執(zhí)行
private static Timer timer = new Timer(); static public class MyTask extends TimerTask { public void run() { System.out.println("運(yùn)行了!時(shí)間為:" + new Date()); } } public static void main(String[] args) throws Exception { MyTask task = new MyTask(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateString = "2015-10-6 12:14:00"; Date dateRef = sdf.parse(dateString); System.out.println("字符串時(shí)間:" + dateRef.toLocaleString() + " 當(dāng)前時(shí)間:" + new Date().toLocaleString()); timer.schedule(task, dateRef); }
看一下運(yùn)行效果:
字符串時(shí)間:2015-10-6 12:14:00 當(dāng)前時(shí)間:2015-10-6 12:13:23 運(yùn)行了!時(shí)間為:Tue Oct 06 12:14:00 CST 2015
執(zhí)行時(shí)間和但前時(shí)間不一致,而是和dateRef的時(shí)間一直,證明了未來執(zhí)行。任務(wù)雖然執(zhí)行完了,但進(jìn)程沒有銷毀,控制臺(tái)上的方框可以看到還是紅色的,看下Timer的源代碼:
public Timer() { this("Timer-" + serialNumber()); }
public Timer(String name) { thread.setName(name); thread.start(); }
所以,啟動(dòng)一個(gè)Timer就是啟動(dòng)一個(gè)新線程,但是這個(gè)新線程并不是守護(hù)線程,所以它會(huì)一直運(yùn)行。要運(yùn)行完就讓進(jìn)程停止的話,設(shè)置Timer為守護(hù)線程就好了,有專門的構(gòu)造函數(shù)可以設(shè)置:
public Timer(boolean isDaemon) { this("Timer-" + serialNumber(), isDaemon); }
public Timer(String name, boolean isDaemon) { thread.setName(name); thread.setDaemon(isDaemon); thread.start(); }
2、計(jì)劃時(shí)間早于當(dāng)前時(shí)間:立即執(zhí)行
如果執(zhí)行任務(wù)的時(shí)間早于當(dāng)前時(shí)間,那么立即執(zhí)行task的任務(wù):
private static Timer timer = new Timer(); static public class MyTask extends TimerTask { public void run() { System.out.println("運(yùn)行了!時(shí)間為:" + new Date()); } } public static void main(String[] args) throws Exception { MyTask task = new MyTask(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateString = "2014-10-6 12:14:00"; Date dateRef = sdf.parse(dateString); System.out.println("字符串時(shí)間:" + dateRef.toLocaleString() + " 當(dāng)前時(shí)間:" + new Date().toLocaleString()); timer.schedule(task, dateRef); }
看一下運(yùn)行效果:
字符串時(shí)間:2014-10-6 12:14:00 當(dāng)前時(shí)間:2015-10-6 12:20:10 運(yùn)行了!時(shí)間為:Tue Oct 06 12:20:10 CST 2015
執(zhí)行時(shí)間和當(dāng)前時(shí)間一致,證明了立即執(zhí)行
3、多個(gè)TimerTask任務(wù)執(zhí)行
Timer中允許有多個(gè)任務(wù):
private static Timer timer = new Timer(); static public class MyTask extends TimerTask { public void run() { System.out.println("運(yùn)行了!時(shí)間為:" + new Date()); } } public static void main(String[] args) throws Exception { MyTask task1 = new MyTask(); MyTask task2 = new MyTask(); SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateString1 = "2015-10-6 12:26:00"; String dateString2 = "2015-10-6 12:27:00"; Date dateRef1 = sdf1.parse(dateString1); Date dateRef2 = sdf2.parse(dateString2); System.out.println("字符串時(shí)間:" + dateRef1.toLocaleString() + " 當(dāng)前時(shí)間:" + new Date().toLocaleString()); System.out.println("字符串時(shí)間:" + dateRef2.toLocaleString() + " 當(dāng)前時(shí)間:" + new Date().toLocaleString()); timer.schedule(task1, dateRef1); timer.schedule(task2, dateRef2); }
看一下運(yùn)行結(jié)果:
字符串時(shí)間:2015-10-612:26:00當(dāng)前時(shí)間:2015-10-612:25:38
字符串時(shí)間:2015-10-612:27:00當(dāng)前時(shí)間:2015-10-612:25:38
運(yùn)行了!時(shí)間為:TueOct0612:26:00CST2015
運(yùn)行了!時(shí)間為:TueOct0612:27:00CST2015
可以看到,運(yùn)行時(shí)間和設(shè)置的時(shí)間一致,證明了未來可以執(zhí)行多個(gè)任務(wù)。另外注意,Task是以隊(duì)列的方式一個(gè)一個(gè)被順序執(zhí)行的,所以執(zhí)行的時(shí)間有可能和預(yù)期的時(shí)間不一致,因?yàn)榍懊娴娜蝿?wù)可能消耗過長,后面任務(wù)的運(yùn)行時(shí)間也有可能被延遲。
代碼就不寫了,舉個(gè)例子,任務(wù)1計(jì)劃12:00:00被執(zhí)行,任務(wù)2計(jì)劃12:00:10被執(zhí)行,結(jié)果任務(wù)1執(zhí)行了30秒,那么任務(wù)2將在12:00:30被執(zhí)行,因?yàn)門ask是被放入隊(duì)列中的,因此必須一個(gè)一個(gè)順序運(yùn)行。
Timer的schedule(TimerTasktask,DatefirstTime,longperiod)
該方法的作用是在指定的日期之后,按指定的間隔周期性地?zé)o限循環(huán)地執(zhí)行某一人物
1、計(jì)劃時(shí)間晚于當(dāng)前時(shí)間:未來執(zhí)行
static public class MyTask extends TimerTask { public void run() { System.out.println("運(yùn)行了!時(shí)間為:" + new Date()); } } public static void main(String[] args) throws Exception { MyTask task = new MyTask(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateString = "2015-10-6 18:00:00"; Timer timer = new Timer(); Date dateRef = sdf.parse(dateString); System.out.println("字符串時(shí)間:" + dateRef.toLocaleString() + " 當(dāng)前時(shí)間:" + new Date().toLocaleString()); timer.schedule(task, dateRef, 4000); }
看一下運(yùn)行結(jié)果:
字符串時(shí)間:2015-10-6 18:01:00 當(dāng)前時(shí)間:2015-10-6 18:00:15
運(yùn)行了!時(shí)間為:Tue Oct 06 18:01:00 CST 2015
運(yùn)行了!時(shí)間為:Tue Oct 06 18:01:04 CST 2015
運(yùn)行了!時(shí)間為:Tue Oct 06 18:01:08 CST 2015
運(yùn)行了!時(shí)間為:Tue Oct 06 18:01:12 CST 2015
...
看到從設(shè)定的時(shí)間開始,每隔4秒打印一次,無限打印下去
2、計(jì)劃時(shí)間早于當(dāng)前時(shí)間:立即執(zhí)行
static public class MyTask extends TimerTask { public void run() { System.out.println("運(yùn)行了!時(shí)間為:" + new Date()); } } public static void main(String[] args) throws Exception { MyTask task = new MyTask(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateString = "2014-10-6 18:01:00"; Timer timer = new Timer(); Date dateRef = sdf.parse(dateString); System.out.println("字符串時(shí)間:" + dateRef.toLocaleString() + " 當(dāng)前時(shí)間:" + new Date().toLocaleString()); timer.schedule(task, dateRef, 4000); }
看一下運(yùn)行結(jié)果:
字符串時(shí)間:2014-10-6 18:01:00 當(dāng)前時(shí)間:2015-10-6 18:02:46
運(yùn)行了!時(shí)間為:Tue Oct 06 18:02:46 CST 2015
運(yùn)行了!時(shí)間為:Tue Oct 06 18:02:50 CST 2015
運(yùn)行了!時(shí)間為:Tue Oct 06 18:02:54 CST 2015
運(yùn)行了!時(shí)間為:Tue Oct 06 18:02:58 CST 2015
運(yùn)行了!時(shí)間為:Tue Oct 06 18:03:02 CST 2015
...
看到運(yùn)行時(shí)間比當(dāng)前時(shí)間早,從當(dāng)前時(shí)間開始,每隔4秒打印一次,無限循環(huán)下去
TimerTask的cancel()方法
TimerTask的cancel()方法的作用是將自身從任務(wù)隊(duì)列中清除:
static public class MyTaskA extends TimerTask { public void run() { System.out.println("A運(yùn)行了!時(shí)間為:" + new Date()); this.cancel(); } } static public class MyTaskB extends TimerTask { public void run() { System.out.println("B運(yùn)行了!時(shí)間為:" + new Date()); } } public static void main(String[] args) throws Exception { MyTaskA taskA = new MyTaskA(); MyTaskB taskB = new MyTaskB(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateString = "2015-10-6 18:10:00"; Timer timer = new Timer(); Date dateRef = sdf.parse(dateString); System.out.println("字符串時(shí)間:" + dateRef.toLocaleString() + " 當(dāng)前時(shí)間:" + new Date().toLocaleString()); timer.schedule(taskA, dateRef, 4000); timer.schedule(taskB, dateRef, 4000); }
看一下運(yùn)行結(jié)果:
字符串時(shí)間:2015-10-6 18:10:00 當(dāng)前時(shí)間:2015-10-6 18:09:47 A運(yùn)行了!時(shí)間為:Tue Oct 06 18:10:00 CST 2015 B運(yùn)行了!時(shí)間為:Tue Oct 06 18:10:00 CST 2015 B運(yùn)行了!時(shí)間為:Tue Oct 06 18:10:04 CST 2015 B運(yùn)行了!時(shí)間為:Tue Oct 06 18:10:08 CST 2015 B運(yùn)行了!時(shí)間為:Tue Oct 06 18:10:12 CST 2015 ...
看到TimeTask的cancel()方法是將自身從任務(wù)隊(duì)列中被移除,其他任務(wù)不受影響
Timer的cancel()方法
把上面代碼改動(dòng)一下:
private static Timer timer = new Timer(); static public class MyTaskA extends TimerTask { public void run() { System.out.println("A運(yùn)行了!時(shí)間為:" + new Date()); timer.cancel(); } } static public class MyTaskB extends TimerTask { public void run() { System.out.println("B運(yùn)行了!時(shí)間為:" + new Date()); } } public static void main(String[] args) throws Exception { MyTaskA taskA = new MyTaskA(); MyTaskB taskB = new MyTaskB(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateString = "2015-10-6 18:10:00"; Date dateRef = sdf.parse(dateString); System.out.println("字符串時(shí)間:" + dateRef.toLocaleString() + " 當(dāng)前時(shí)間:" + new Date().toLocaleString()); timer.schedule(taskA, dateRef, 4000); timer.schedule(taskB, dateRef, 4000); }
看一下運(yùn)行結(jié)果:
字符串時(shí)間:2015-10-618:10:00當(dāng)前時(shí)間:2015-10-618:14:15
A運(yùn)行了!時(shí)間為:TueOct0618:14:15CST2015
全部任務(wù)都被清除,并且進(jìn)程被銷毀。不過注意一下,cancel()方法未必一定會(huì)停止執(zhí)行計(jì)劃任務(wù),可能正常執(zhí)行,因?yàn)閏ancel()方法會(huì)嘗試去獲取queue鎖,如果并沒有獲取到queue鎖的話,TimerTask類中的任務(wù)繼續(xù)執(zhí)行也是完全有可能的
其他方法
再列舉一些Timer中的其他schedule的重載方法的作用,就不提供證明的代碼了,可以自己嘗試一下:
1、schedule(TimerTasktask,longdelay)
以當(dāng)前時(shí)間為參考,在此時(shí)間基礎(chǔ)上延遲指定的毫秒數(shù)后執(zhí)行一次TimerTask任務(wù)
2、schedule(TimerTasktask,longdelay,longperiod)
以當(dāng)前時(shí)間為參考,在此時(shí)間基礎(chǔ)上延遲指定的毫秒數(shù)后,以period為循環(huán)周期,循環(huán)執(zhí)行TimerTask任務(wù)
3、scheduleAtFixedRate(TimerTasktask,DatefirstTime,longperiod)
在延時(shí)的場景下,schedule方法和scheduleAtFixedRate方法沒有區(qū)別,它們的區(qū)別只是在非延時(shí)上。如果執(zhí)行任務(wù)的時(shí)間沒有被延時(shí),對(duì)于schedule方法來說,下一次任務(wù)執(zhí)行的時(shí)間參考的是上一次任務(wù)的開始時(shí)間來計(jì)算的;對(duì)于scheduleAtFixedRate方法來說,下一次任務(wù)執(zhí)行的時(shí)間參考的是上一次任務(wù)的結(jié)束時(shí)間來計(jì)算的
總結(jié)
以上就是本文關(guān)于Java多線程定時(shí)器Timer原理及實(shí)現(xiàn)的全部內(nèi)容,希望對(duì)大家有所幫助。感興趣的朋友可以繼續(xù)參閱本站:
Java多線程之readwritelock讀寫分離的實(shí)現(xiàn)代碼
如有不足之處,歡迎留言指出。
相關(guān)文章
Java Vector和ArrayList的異同分析及實(shí)例講解
在本篇文章里小編給大家整理的是一篇關(guān)于Java Vector和ArrayList的異同分析及實(shí)例講解內(nèi)容,有興趣的朋友們可以學(xué)習(xí)參考下。2021-01-01Java模擬HTTP Get Post請(qǐng)求實(shí)現(xiàn)論壇自動(dòng)回帖功能
這篇文章主要介紹了Java模擬HTTP Get Post請(qǐng)求實(shí)現(xiàn)論壇自動(dòng)回帖功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-09-09Spring?Security實(shí)現(xiàn)統(tǒng)一登錄與權(quán)限控制的示例代碼
這篇文章主要介紹了Spring?Security實(shí)現(xiàn)統(tǒng)一登錄與權(quán)限控制,本文通過示例代碼重點(diǎn)看一下統(tǒng)一認(rèn)證中心和業(yè)務(wù)網(wǎng)關(guān)的建設(shè),需要的朋友可以參考下2022-03-03詳解mybatis-plus配置找不到Mapper接口路徑的坑
這篇文章主要介紹了詳解mybatis-plus配置找不到Mapper接口路徑的坑,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-10-10springboot 在xml里讀取yml的配置信息的示例代碼
這篇文章主要介紹了springboot 在xml里讀取yml的配置信息的示例代碼,代碼簡單易懂,對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-09-09