Java?Timer使用講解
Timer 詳解
Timer
和 TimerTask
用于在后臺線程中調(diào)度任務(wù)的 java.util
類。 TimerTask
負責任務(wù)的執(zhí)行, Timer
負責任務(wù)的調(diào)度。
定時功能
Timer
提供了三種定時模式:
fixed delay fixed rate
java.util包下提供了對定時任務(wù)的支持,涉及2個類:
- Timer:定時器類
- TimerTask:任務(wù)抽象類
使用該定時任務(wù)我們需要繼承TimerTask抽象類,覆蓋run方法編寫任務(wù)執(zhí)行代碼,并利用Timer定時器對TimerTask進行調(diào)度。
編寫一個任務(wù):
TimerTask task = new TimerTask() { @Override public void run() { System.out.println(DateUtil.formatNow() + " " + Thread.currentThread().getName() + " task run "); } };
接著使用Timer對TimerTask進行調(diào)度,Timer提供了多種方法,可分為一次性任務(wù)和可重復(fù)執(zhí)行任務(wù)。
一、一次性任務(wù)
一次性任務(wù)是指Timer執(zhí)行一次之后,該任務(wù)后續(xù)不再執(zhí)行。
一次性任務(wù)包括2個方法,如下:
- voidschedule(TimerTasktask,longdelay):延遲delay毫秒后執(zhí)行一次task
- voidschedule(TimerTasktask,Datetime):在指定時間time執(zhí)行一次task,如果time過期,將會立即執(zhí)行
二、可重復(fù)執(zhí)行任務(wù)
可重復(fù)執(zhí)行任務(wù)是指,任務(wù)允許按照設(shè)定的規(guī)則重復(fù)執(zhí)行。
可重復(fù)執(zhí)行任務(wù)共有4個方法,分為固定延時 schedule和固定速率 scheduleAtFixedRate:
- voidschedule(TimerTasktask,longdelay,longperiod):延遲delay毫秒后執(zhí)行task,之后每隔period毫秒執(zhí)行一次task
- voidschedule(TimerTasktask,DatefirstTime,longperiod):在指定時間time執(zhí)行一次task,之后每隔period毫秒執(zhí)行一次task
- voidscheduleAtFixedRate(TimerTasktask,longdelay,longperiod):延遲delay毫秒后執(zhí)行task,之后每隔period毫秒執(zhí)行一次task
- voidscheduleAtFixedRate(TimerTasktask,DatefirstTime,longperiod):在指定時間time執(zhí)行一次task,之后每隔period毫秒執(zhí)行一次task
示例1:schedule方法,延遲delay毫秒后執(zhí)行task,之后每隔period毫秒執(zhí)行一次task
System.out.println("啟動于:" + DateUtil.formatNow()); Timer timer = new Timer("timer"); timer.schedule(task, 1000, 2000);
輸出:
啟動于:2022-10-31 10:05:15
2022-10-31 10:05:16 timer task run
2022-10-31 10:05:18 timer task run
2022-10-31 10:05:20 timer task run
示例2:schedule在指定時間time執(zhí)行一次task,之后每隔period毫秒執(zhí)行一次task
System.out.println("啟動于:" + DateUtil.formatNow()); Timer timer = new Timer("timer"); timer.schedule(task, DateUtil.parse("2022-10-31 10:07:00", DateUtil.YYYY_MM_DD_HH24_MM_SS), 2000);
輸出:
啟動于:2022-10-31 10:06:39
2022-10-31 10:07:00 timer task run
2022-10-31 10:07:02 timer task run
2022-10-31 10:07:04 timer task run
固定延時 schedule 和 固定速率 scheduleAtFixedRate 在正常情況下看起來功能基本是一致的,區(qū)別在于當任務(wù)耗時超出執(zhí)行時間間隔period,后續(xù)任務(wù)被延誤時,schedule和scheduleAtFixedRate的處理方式不同,后面介紹。
三、固定延時和固定速率區(qū)別(重點)
1. 介紹
由于Timer內(nèi)部僅維護一個線程來執(zhí)行所有任務(wù),所以當前一個任務(wù)耗時過長,可能會導(dǎo)致后一個任務(wù)的執(zhí)行被延誤。
出現(xiàn)任務(wù)延誤的情況下,固定延時 schedule和 固定速率 scheduleAtFixedRate 的區(qū)別就在于,schedule會順延,而scheduleAtFixedRate會把延誤任務(wù)立馬補上。
在網(wǎng)上看到幾個非常恰當?shù)睦樱N上來加深理解。
例1:
暑假到了老師給schedule和scheduleAtFixedRate兩個同學布置作業(yè)。
老師要求學生暑假每天寫2頁,30天后完成作業(yè)。
這兩個學生每天按時完成作業(yè),直到第10天,出了意外,兩個學生出去旅游花了5天時間,這5天時間里兩個人都沒有做作業(yè)。任務(wù)被拖延了。
這時候兩個學生采取的策略就不同了:
schedule重新安排了任務(wù)時間,旅游回來的第一天做第11天的任務(wù),第二天做第12天的任務(wù),最后完成任務(wù)花了35天。
scheduleAtFixedRate是個守時的學生,她總想按時完成老師的任務(wù),于是在旅游回來的第一天把之前5天欠下的任務(wù)以及第16天當天的任務(wù)全部完成了,之后還是按照老師的原安排完成作業(yè),最后完成任務(wù)花了30天。
例2:
固定速率就好比你今天加班到很晚,但是到了第二天還必須準點到公司上班,如果你一不小心加班到了第二天早上 9 點,你就連休息的時間都沒有了。
而固定時延的意思是你必須睡夠 8 個小時再過來上班,如果你加班到凌晨 6 點,那就可以下午過來上班了。
固定速率強調(diào)準點,固定時延強調(diào)間隔。
如果任務(wù)必須每天準點調(diào)度,那就應(yīng)該使用固定速率調(diào)度,并且要確保每個任務(wù)執(zhí)行時間不要太長,避免超過period間隔。
如果任務(wù)需要每隔幾分鐘跑一次,那就使用固定時延調(diào)度,它不是很在乎單個任務(wù)要跑多長時間。
我們來模擬一下這個情況。
首先,我們對TimerTask進行修改,讓它某一次任務(wù)產(chǎn)生大量耗時:
TimerTask task = new TimerTask() { private int i = 1; @Override public void run() { System.out.print(i + " " + DateUtil.formatNow() + " 開始執(zhí)行, "); if(i == 3) { ThreadUtil.sleep(11 * 1000); } System.out.println(DateUtil.formatNow() + " 結(jié)束"); i++; } };
該任務(wù)在執(zhí)行第3次時,將會休眠11秒,這將會導(dǎo)致延誤后續(xù)的任務(wù)。
2. 固定速率
示例:
Timer timer = new Timer("timer"); timer.scheduleAtFixedRate(task, 5000, 2000);
設(shè)定任務(wù)延遲5秒后執(zhí)行第1次任務(wù),之后每2秒執(zhí)行一次。
輸出:
啟動于:2022-10-31 15:51:24
1 2022-10-31 15:51:29 開始執(zhí)行, 2022-10-31 15:51:29 結(jié)束
2 2022-10-31 15:51:31 開始執(zhí)行, 2022-10-31 15:51:31 結(jié)束
3 2022-10-31 15:51:33 開始執(zhí)行, 2022-10-31 15:51:44 結(jié)束 *
4 2022-10-31 15:51:44 開始執(zhí)行, 2022-10-31 15:51:44 結(jié)束 *
5 2022-10-31 15:51:44 開始執(zhí)行, 2022-10-31 15:51:44 結(jié)束 *
6 2022-10-31 15:51:44 開始執(zhí)行, 2022-10-31 15:51:44 結(jié)束 *
7 2022-10-31 15:51:44 開始執(zhí)行, 2022-10-31 15:51:44 結(jié)束 *
8 2022-10-31 15:51:44 開始執(zhí)行, 2022-10-31 15:51:44 結(jié)束 *
9 2022-10-31 15:51:45 開始執(zhí)行, 2022-10-31 15:51:45 結(jié)束
10 2022-10-31 15:51:47 開始執(zhí)行, 2022-10-31 15:51:47 結(jié)束
11 2022-10-31 15:51:49 開始執(zhí)行, 2022-10-31 15:51:49 結(jié)束
如果不存在第3次耗時11秒的情況下,正常任務(wù)執(zhí)行時間應(yīng)該為:
啟動于:2022-10-31 15:51:24
1 2022-10-31 15:51:29 開始執(zhí)行, 2022-10-31 15:51:29 結(jié)束
2 2022-10-31 15:51:31 開始執(zhí)行, 2022-10-31 15:51:31 結(jié)束
3 2022-10-31 15:51:33 開始執(zhí)行, 2022-10-31 15:51:33 結(jié)束 *
4 2022-10-31 15:51:35 開始執(zhí)行, 2022-10-31 15:51:35 結(jié)束 *
5 2022-10-31 15:51:37 開始執(zhí)行, 2022-10-31 15:51:37 結(jié)束 *
6 2022-10-31 15:51:39 開始執(zhí)行, 2022-10-31 15:51:39 結(jié)束 *
7 2022-10-31 15:51:41 開始執(zhí)行, 2022-10-31 15:51:41 結(jié)束 *
8 2022-10-31 15:51:43 開始執(zhí)行, 2022-10-31 15:51:43 結(jié)束 *
9 2022-10-31 15:51:45 開始執(zhí)行, 2022-10-31 15:51:45 結(jié)束
10 2022-10-31 15:51:47 開始執(zhí)行, 2022-10-31 15:51:47 結(jié)束
11 2022-10-31 15:51:49 開始執(zhí)行, 2022-10-31 15:51:49 結(jié)束
但是在第3次執(zhí)行任務(wù)時因為執(zhí)行耗時11秒,第4次本該在15:51:35開始執(zhí)行并完成任務(wù),卻到了15:51:44才執(zhí)行完成,這11秒延誤了后續(xù)5個任務(wù)的正常執(zhí)行,因此在15:51:44時,scheduleAtFixedRate趕作業(yè)把延誤的5個任務(wù)一起執(zhí)行了。
最后趕上了原本的進度,第9個任務(wù)準時在15:51:45執(zhí)行。
3. 固定延時
示例:
Timer timer = new Timer("timer"); timer.schedule(task, 5000, 2000);
輸出:
啟動于:2022-10-31 15:56:59
1 2022-10-31 15:57:04 開始執(zhí)行, 2022-10-31 15:57:04 結(jié)束
2 2022-10-31 15:57:06 開始執(zhí)行, 2022-10-31 15:57:06 結(jié)束
3 2022-10-31 15:57:08 開始執(zhí)行, 2022-10-31 15:57:19 結(jié)束 *
4 2022-10-31 15:57:19 開始執(zhí)行, 2022-10-31 15:57:19 結(jié)束
5 2022-10-31 15:57:21 開始執(zhí)行, 2022-10-31 15:57:21 結(jié)束
6 2022-10-31 15:57:24 開始執(zhí)行, 2022-10-31 15:57:24 結(jié)束
7 2022-10-31 15:57:26 開始執(zhí)行, 2022-10-31 15:57:26 結(jié)束
8 2022-10-31 15:57:28 開始執(zhí)行, 2022-10-31 15:57:28 結(jié)束
9 2022-10-31 15:57:30 開始執(zhí)行, 2022-10-31 15:57:30 結(jié)束
10 2022-10-31 15:57:32 開始執(zhí)行, 2022-10-31 15:57:32 結(jié)束
如果不存在第3次耗時11秒的情況下,正常任務(wù)執(zhí)行時間應(yīng)該為:
啟動于:2022-10-31 15:56:59
1 2022-10-31 15:57:04 開始執(zhí)行, 2022-10-31 15:57:04 結(jié)束
2 2022-10-31 15:57:06 開始執(zhí)行, 2022-10-31 15:57:06 結(jié)束
3 2022-10-31 15:57:08 開始執(zhí)行, 2022-10-31 15:57:08 結(jié)束 *
4 2022-10-31 15:57:10 開始執(zhí)行, 2022-10-31 15:57:10 結(jié)束
5 2022-10-31 15:57:12 開始執(zhí)行, 2022-10-31 15:57:12 結(jié)束
6 2022-10-31 15:57:14 開始執(zhí)行, 2022-10-31 15:57:14 結(jié)束
7 2022-10-31 15:57:16 開始執(zhí)行, 2022-10-31 15:57:16 結(jié)束
8 2022-10-31 15:57:18 開始執(zhí)行, 2022-10-31 15:57:18 結(jié)束
9 2022-10-31 15:57:20 開始執(zhí)行, 2022-10-31 15:57:20 結(jié)束
10 2022-10-31 15:57:22 開始執(zhí)行, 2022-10-31 15:57:22 結(jié)束
使用schedule調(diào)度,第4次任務(wù)本該在15:57:10開始執(zhí)行,但由于耗時11秒直到15:57:19才開始。
而第3次任務(wù)實際是在19秒完成,完成后又在19秒立即執(zhí)行第4次,中間少了2秒間隔,第4次完成后接著開始2秒一次,變?yōu)榱藦?1秒開始執(zhí)行第5次。
和我原本的推測不一樣的是,本以為19秒完成后,第4次會隔2秒在21秒執(zhí)行,沒想到19秒會立即執(zhí)行。
猜測與delay參數(shù)有關(guān),但調(diào)整了delay后仍然一樣,完成的那一秒還是會馬上再執(zhí)行第4次任務(wù)。
通過以上測試對比,我們可以感受到Timer中固定速率和固定延時的區(qū)別,但為了避免出錯,使用Timer時應(yīng)讓TimerTask耗時盡可能短。
4. 其他要點
1.以上是僅第3次任務(wù)加上了耗時11秒,如果是所有任務(wù)都耗時11秒呢?
如果每次任務(wù)執(zhí)行都耗時11秒,那么無論是固定速率還是固定延時,都將是11秒執(zhí)行一個任務(wù)。
2.如果改為schedule(TimerTasktask,DatefirstTime,longperiod)和scheduleAtFixedRate(TimerTasktask,DatefirstTime,longperiod)來調(diào)度任務(wù),firstTime指定為10點,而當前系統(tǒng)時間為11點,會出現(xiàn)什么情況呢?
雖然firstTime已經(jīng)過期,但是Timer將會立即開始執(zhí)行任務(wù),之后按照period間隔重復(fù)執(zhí)行任務(wù)。
3.如果TimerTask執(zhí)行過程中拋出了異常會發(fā)生什么事情?
Timer內(nèi)部僅維護一個線程,當任一TimerTask拋出異常,將導(dǎo)致此線程終止運行,該Timer負責的所有任務(wù)都無法執(zhí)行。
四、調(diào)度多個TimerTask
在上一節(jié)中,介紹的是一個可重復(fù)執(zhí)行的TimeTask,如果執(zhí)行耗時大于設(shè)定的間隔period,將會影響該TimerTask下一次執(zhí)行的時間點。
而這一節(jié)則是為了單獨說明,一個Timer同時調(diào)度多個TimeTask也會互相影響。
示例:
TimerTask task1 = new TimerTask() { private int i = 1; @Override public void run() { System.out.print(i + " task1:" + DateUtil.formatNow() + " 開始執(zhí)行, "); ThreadUtil.sleep(11 * 1000); System.out.println(DateUtil.formatNow() + " 結(jié)束"); i++; } }; TimerTask task2 = new TimerTask() { private int i = 1; @Override public void run() { System.out.print(i + " task2:" + DateUtil.formatNow() + " 開始執(zhí)行, "); ThreadUtil.sleep(11 * 1000); System.out.println(DateUtil.formatNow() + " 結(jié)束"); i++; } }; Timer timer = new Timer("timer"); timer.scheduleAtFixedRate(task1, 5000, 2000); timer.scheduleAtFixedRate(task2, 5000, 2000);
輸出:
1 task1:2022-10-31 16:58:27 開始執(zhí)行, 2022-10-31 16:58:38 結(jié)束
1 task2:2022-10-31 16:58:38 開始執(zhí)行, 2022-10-31 16:58:49 結(jié)束
2 task2:2022-10-31 16:58:49 開始執(zhí)行, 2022-10-31 16:59:00 結(jié)束
2 task1:2022-10-31 16:59:00 開始執(zhí)行, 2022-10-31 16:59:11 結(jié)束
3 task1:2022-10-31 16:59:11 開始執(zhí)行, 2022-10-31 16:59:22 結(jié)束
3 task2:2022-10-31 16:59:22 開始執(zhí)行, 2022-10-31 16:59:33 結(jié)束
4 task2:2022-10-31 16:59:33 開始執(zhí)行, 2022-10-31 16:59:44 結(jié)束
4 task1:2022-10-31 16:59:44 開始執(zhí)行, 2022-10-31 16:59:55 結(jié)束
可以發(fā)現(xiàn),task1和task2其實都沒有按照既定時間去執(zhí)行任務(wù)了。
根本原因是在于,Timer內(nèi)部僅維護一個線程執(zhí)行所有TimerTask,為了避免錯誤,一個Timer對象最好僅調(diào)度一個TimerTask對象,除非可以確保多個TimerTask之間一定不會相互影響。
因此編寫TimerTask時應(yīng)當自行捕獲異常。
五、取消任務(wù)
Timer在創(chuàng)建時實際上是默認在內(nèi)部維護了一個非守護線程,即使任務(wù)全部執(zhí)行完成,線程也并不會銷毀。
Timer提供cancel()方法,可以手動調(diào)用取消定時器所有的任務(wù),并銷毀定時器。
如果想要Timer內(nèi)部創(chuàng)建的是守護線程,可以使用以下構(gòu)造方法創(chuàng)建定時器,設(shè)置isDaemon為true:
- Timer(booleanisDaemon)
Timer(Stringname,booleanisDaemon)
如果沒有自己定義name參數(shù),默認Timer內(nèi)部自動命名為“Timer-遞增序號”,作為內(nèi)部線程的線程名稱,在構(gòu)造方法內(nèi)啟動此線程。
如果是要取消單個任務(wù),可以使用TimerTask的cancel()方法。
當TimerTask調(diào)用cancel之后,任務(wù)是取消了,但Timer自身并不能馬上知道TimerTask被取消,而是在準備執(zhí)行前才知道,因此Timer內(nèi)部還維護著這個任務(wù)的引用。若希望Timer立即清除引用,可調(diào)用Timer.purge()立即執(zhí)行清除。
到此這篇關(guān)于Java Timer使用講解的文章就介紹到這了,更多相關(guān)Java Timer使用內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot如何用java生成靜態(tài)html
這篇文章主要介紹了SpringBoot如何用java生成靜態(tài)html,文章圍繞主題展開詳細的內(nèi)容介紹,需要的朋友可以參考一下2022-06-06在SSM框架中將圖片上傳到數(shù)據(jù)庫中的實現(xiàn)代碼
這篇文章主要介紹了在SSM框架中將圖片上傳到數(shù)據(jù)庫中的實現(xiàn)代碼,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-03-03Java8中List轉(zhuǎn)換String字符串幾種方式
這篇文章主要給大家介紹了關(guān)于Java8中List轉(zhuǎn)換String字符串的幾種方式,在實際開發(fā)中經(jīng)常遇到List轉(zhuǎn)為String字符串的情況,文中給出了幾種方法的示例代碼,需要的朋友可以參考下2023-07-07SpringBoot自定義全局異常處理器的問題總結(jié)
Springboot框架提供兩個注解幫助我們十分方便實現(xiàn)全局異常處理器以及自定義異常,處理器會優(yōu)先處理更具體的異常類型,如果沒有找到匹配的處理器,那么它會尋找處理更一般異常類型的處理器,本文介紹SpringBoot自定義全局異常處理器的問題,一起看看吧2024-01-01java模擬http的Get/Post請求,并設(shè)置ip與port代理的方法
下面小編就為大家?guī)硪黄猨ava模擬http的Get/Post請求,并設(shè)置ip與port代理的方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-02-02