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

