Java多線程案例之定時(shí)器詳解
一.什么是定時(shí)器
定時(shí)器也是軟件開(kāi)發(fā)中的一個(gè)重要組件. 類似于一個(gè) “鬧鐘”. 達(dá)到一個(gè)設(shè)定的時(shí)間之后, 就執(zhí)行某個(gè)指定好的代碼
定時(shí)器是一種實(shí)際開(kāi)發(fā)中非常常用的組件,我們舉幾個(gè)例子:
1.比如網(wǎng)絡(luò)通信中, 如果對(duì)方 500ms 內(nèi)沒(méi)有返回?cái)?shù)據(jù), 則斷開(kāi)連接嘗試重連
2.比如一個(gè) Map, 希望里面的某個(gè) key 在 3s 之后過(guò)期(自動(dòng)刪除)
以上類似于這樣的場(chǎng)景就需要用到定時(shí)器

二.標(biāo)準(zhǔn)庫(kù)中的定時(shí)器(timer)
2.1什么是定時(shí)器
標(biāo)準(zhǔn)庫(kù)中供了一個(gè) Timer 類. Timer 類的核心方法為 schedule ,schedule 包含兩個(gè)參數(shù). 第一個(gè)參數(shù)指定即將要執(zhí)行的任務(wù)代碼TimerTask, 第二個(gè)參數(shù)指定多長(zhǎng)時(shí)間之后執(zhí)行 (單位為毫秒).
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("hello");
}
}, 3000);
2.2定時(shí)器的使用
Timer的構(gòu)造方法
| 構(gòu)造方法 | 說(shuō)明 |
|---|---|
| public Timer() | 無(wú)參數(shù)構(gòu)造方法,默認(rèn)定時(shí)器關(guān)聯(lián)的線程不是守護(hù)線程,線程名字也是默認(rèn)值 |
| public Timer(boolean isDaemon) | 指定定時(shí)器中關(guān)聯(lián)的線程是否為守護(hù)線程,如果是,參數(shù)為true |
| public Timer(String name) | 指定定時(shí)器關(guān)聯(lián)線程名稱,線程類型默認(rèn)為非守護(hù)線程 |
| public Timer(String name, boolean isDaemon) | 指定定時(shí)器關(guān)聯(lián)線程名和線程類型 |
Timer方法
| 方法 | 說(shuō)明 |
|---|---|
| public void schedule (TimerTask task, long delay) | 指定任務(wù),延遲多久執(zhí)行該任務(wù) |
| public void schedule(TimerTask task, Date time) | 指定任務(wù),指定任務(wù)的執(zhí)行時(shí)間 |
| public void schedule(TimerTask task, long delay, long period) | 連續(xù)執(zhí)行指定任務(wù),延遲時(shí)間,連續(xù)執(zhí)行任務(wù)的時(shí)間間隔,毫秒為單位 |
| public void schedule(TimerTask task, Date firstTime, long period) | 連續(xù)執(zhí)行指定任務(wù),第一次任務(wù)的執(zhí)行時(shí)間,連續(xù)執(zhí)行任務(wù)的時(shí)間間隔 |
| public void scheduleAtFixedRate(TimerTask task, Date firstTime, long period) | 連續(xù)執(zhí)行指定任務(wù),第一次任務(wù)的執(zhí)行時(shí)間,連續(xù)執(zhí)行任務(wù)的時(shí)間間隔 |
| public void scheduleAtFixedRate(TimerTask task, long delay, long period) | 連續(xù)執(zhí)行指定任務(wù),延遲時(shí)間,連續(xù)執(zhí)行任務(wù)的時(shí)間間隔,毫秒為單位 |
| public void cancel() | 終止定時(shí)器所有任務(wù),終止執(zhí)行的任務(wù)不受影響 |
TimerTask是專門(mén)來(lái)實(shí)現(xiàn)Runnable接口的
下面我們會(huì)實(shí)現(xiàn)一下定時(shí)器,我們就不用TimerTask了,我們直接使用Runnable,因?yàn)門(mén)imerTask實(shí)現(xiàn)了Runnable接口,所以后面測(cè)試我們自己所寫(xiě)的schedule方法時(shí),也可以傳入TimerTask類型的引用,既然是簡(jiǎn)單地實(shí)現(xiàn),那就不實(shí)現(xiàn)連續(xù)執(zhí)行的功能了。.
public class Test {
public static void main(String[] args){
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("執(zhí)行線程在5s后執(zhí)行");
}
},5000);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("執(zhí)行線程在2s后執(zhí)行");
}
},2000);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("執(zhí)行線程在3s后執(zhí)行");
}
},3000);
}
}

三.實(shí)現(xiàn)定時(shí)器
3.1什么是定時(shí)器
定時(shí)器的構(gòu)成:一個(gè)帶優(yōu)先級(jí)的阻塞隊(duì)列
為啥要帶優(yōu)先級(jí)呢?
因?yàn)樽枞?duì)列中的任務(wù)都有各自的執(zhí)行時(shí)刻 (delay). 最先執(zhí)行的任務(wù)一定是 delay 最小的. 使用帶優(yōu)先級(jí)的隊(duì)列就可以高效的把這個(gè) delay 最小的任務(wù)找出來(lái).
1.隊(duì)列中的每個(gè)元素是一個(gè) Task 對(duì)象,Task 中帶有一個(gè)時(shí)間屬性, 隊(duì)首元素就是即將同時(shí)有一個(gè) worker 線程一直掃描隊(duì)首元素, 看隊(duì)首元素是否需要執(zhí)行
class MyTask implements Comparable<MyTask>{
//執(zhí)行的時(shí)間戳
private long time;
//接受具體任務(wù)
private Runnable runnable;
//創(chuàng)建MyTask構(gòu)造方法
public MyTask(Runnable runnable,long time) {
//通過(guò)currentTimeMillis來(lái)獲取time 中存的是絕對(duì)時(shí)間, 超過(guò)這個(gè)時(shí)間的任務(wù)就應(yīng)該被執(zhí)行
this.time = System.currentTimeMillis()+time;
this.runnable = runnable;
}
//執(zhí)行任務(wù)
public void run(){
this.runnable.run();
}
//提供對(duì)外time
public long getTime() {
return time;
}
//執(zhí)行comparable接口來(lái)進(jìn)行時(shí)間的比較,并將time的long類型轉(zhuǎn)換為int類型
@Override
public int compareTo(MyTask o) {
return (int)(this.time-o.time);
}
}
Timer 實(shí)例中, 通過(guò) PriorityBlockingQueue 來(lái)組織若干個(gè) Task 對(duì)象.通過(guò) schedule 來(lái)往隊(duì)列中插入一個(gè)個(gè) Task 對(duì)象.
class MyTimer{
// 定時(shí)器內(nèi)部要能夠存放多個(gè)任務(wù)
private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();
//為鎖創(chuàng)建一個(gè)對(duì)象
Object locker = new Object();
public void schedule(Runnable runnable, long delay) {
MyTask task = new MyTask(runnable, delay);
queue.put(task);
// 每次任務(wù)插入成功之后, 都喚醒一下掃描線程, 讓線程重新檢查一下隊(duì)首的任務(wù)看是否時(shí)間到要執(zhí)行~~
synchronized (locker) {
locker.notify();
}
}
Timer 類中存在一個(gè) worker 線程, 一直不停的掃描隊(duì)首元素, 看看是否能執(zhí)行這個(gè)任務(wù).所謂 “能執(zhí)行” 指的是該任務(wù)設(shè)定的時(shí)間已經(jīng)到達(dá)了
class MyTimer{
// 定時(shí)器內(nèi)部要能夠存放多個(gè)任務(wù)
private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();
//為鎖創(chuàng)建一個(gè)對(duì)象
Object locker = new Object();
public void schedule(Runnable runnable, long delay) {
MyTask task = new MyTask(runnable, delay);
queue.put(task);
// 每次任務(wù)插入成功之后, 都喚醒一下掃描線程, 讓線程重新檢查一下隊(duì)首的任務(wù)看是否時(shí)間到要執(zhí)行~~
synchronized (locker) {
locker.notify();
}
}
public MyTimer() {
Thread t = new Thread(() -> {
while (true) {
try {
// 先取出隊(duì)首元素
MyTask task = queue.take();
// 再比較一下看看當(dāng)前這個(gè)任務(wù)時(shí)間到了沒(méi)?
long curTime = System.currentTimeMillis();
if (curTime < task.getTime()) {
// 時(shí)間沒(méi)到, 把任務(wù)再塞回到隊(duì)列中.
queue.put(task);
// 指定一個(gè)等待時(shí)間,防止有的線程需要等待時(shí)間很長(zhǎng),但是線程一直運(yùn)行等待時(shí)間到來(lái)執(zhí)行,這樣會(huì)占有CPU占有資源
synchronized (locker) {
locker.wait(task.getTime() - curTime);
}
} else {
// 時(shí)間到了, 執(zhí)行這個(gè)任務(wù)
task.run();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
}
}
3.2最終實(shí)現(xiàn)代碼
package thread;
import java.util.PriorityQueue;
import java.util.concurrent.PriorityBlockingQueue;
// 創(chuàng)建一個(gè)類, 表示一個(gè)任務(wù).
class MyTask implements Comparable<MyTask> {
// 任務(wù)具體要干啥
private Runnable runnable;
// 任務(wù)具體啥時(shí)候干. 保存任務(wù)要執(zhí)行的毫秒級(jí)時(shí)間戳
private long time;
// after 是一個(gè)時(shí)間間隔. 不是絕對(duì)的時(shí)間戳的值
public MyTask(Runnable runnable, long delay) {
this.runnable = runnable;
this.time = System.currentTimeMillis() + delay;
}
public void run() {
runnable.run();
}
public long getTime() {
return time;
}
@Override
public int compareTo(MyTask o) {
// 到底是誰(shuí)見(jiàn)誰(shuí), 才是一個(gè)時(shí)間小的在前? 需要咱們背下來(lái).
return (int) (this.time - o.time);
}
}
class MyTimer {
// 定時(shí)器內(nèi)部要能夠存放多個(gè)任務(wù)
private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();
public void schedule(Runnable runnable, long delay) {
MyTask task = new MyTask(runnable, delay);
queue.put(task);
// 每次任務(wù)插入成功之后, 都喚醒一下掃描線程, 讓線程重新檢查一下隊(duì)首的任務(wù)看是否時(shí)間到要執(zhí)行~~
synchronized (locker) {
locker.notify();
}
}
private Object locker = new Object();
public MyTimer() {
Thread t = new Thread(() -> {
while (true) {
try {
// 先取出隊(duì)首元素
MyTask task = queue.take();
// 再比較一下看看當(dāng)前這個(gè)任務(wù)時(shí)間到了沒(méi)?
long curTime = System.currentTimeMillis();
if (curTime < task.getTime()) {
// 時(shí)間沒(méi)到, 把任務(wù)再塞回到隊(duì)列中.
queue.put(task);
// 指定一個(gè)等待時(shí)間
synchronized (locker) {
locker.wait(task.getTime() - curTime);
}
} else {
// 時(shí)間到了, 執(zhí)行這個(gè)任務(wù)
task.run();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
}
}
public class Test {
public static void main(String[] args) {
MyTimer myTimer = new MyTimer();
myTimer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("hello timer!");
}
}, 3000);
System.out.println("main");
}
}
以上就是Java多線程案例之定時(shí)器詳解的詳細(xì)內(nèi)容,更多關(guān)于Java多線程 定時(shí)器的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
關(guān)于BigDecimal類型數(shù)據(jù)的絕對(duì)值和相除求百分比
這篇文章主要介紹了關(guān)于BigDecimal類型數(shù)據(jù)的絕對(duì)值和相除求百分比,Java在java.math包中提供的API類BigDecimal,用來(lái)對(duì)超過(guò)16位有效位的數(shù)進(jìn)行精確的運(yùn)算,需要的朋友可以參考下2023-07-07
Springboot?對(duì)接支付寶實(shí)現(xiàn)掃碼支付功能
本文介紹了如何在Spring?Boot項(xiàng)目中實(shí)現(xiàn)支付寶支付功能,包括沙箱環(huán)境配置、依賴引入、配置參數(shù)、訂單類定義、測(cè)試接口編寫(xiě)等步驟,通過(guò)不同場(chǎng)景下的請(qǐng)求方式(PC端、二維碼、回調(diào)處理、定時(shí)查詢支付結(jié)果),展示了如何與支付寶API進(jìn)行交互,感興趣的朋友一起看看吧2025-03-03
Java StackTraceElement實(shí)例代碼
這篇文章主要介紹了Java StackTraceElement實(shí)例代碼,分享了相關(guān)代碼示例,小編覺(jué)得還是挺不錯(cuò)的,具有一定借鑒價(jià)值,需要的朋友可以參考下2018-02-02
maven-surefire-plugin總結(jié)示例詳解
這篇文章主要介紹了maven-surefire-plugin總結(jié),本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-07-07
java郵件發(fā)送簡(jiǎn)單實(shí)現(xiàn)代碼
這篇文章主要為大家詳細(xì)介紹了java郵件發(fā)送簡(jiǎn)單實(shí)現(xiàn)代碼,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-03-03
Java實(shí)戰(zhàn)練習(xí)之撲克牌魔術(shù)
這篇文章主要介紹了Java實(shí)戰(zhàn)練習(xí)之撲克牌魔術(shù),文中有非常詳細(xì)的代碼示例,對(duì)正在學(xué)習(xí)java的小伙伴們有很好地幫助,需要的朋友可以參考下2021-04-04
java實(shí)現(xiàn)清理DNS Cache的方法
這篇文章主要介紹了java實(shí)現(xiàn)清理DNS Cache的方法,分析了幾種常用的清理方法,并給出了反射清理的完整實(shí)例,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-01-01
SpringBoot集成分頁(yè)插件PageHelper的配置和使用過(guò)程
這篇文章主要介紹了SpringBoot集成分頁(yè)插件PageHelper的配置和使用過(guò)程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-04-04
搭建Spring Boot聚合項(xiàng)目的實(shí)現(xiàn)示例
本文主要介紹了搭建Spring Boot聚合項(xiàng)目的實(shí)現(xiàn)示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2025-04-04

