??定時(shí)器是什么
定時(shí)器也是軟件開發(fā)中的一個(gè)重要組件. 類似于一個(gè) “鬧鐘”. 達(dá)到一個(gè)設(shè)定的時(shí)間之后, 就執(zhí)行某個(gè)指定好的代碼
定時(shí)器是一種實(shí)際開發(fā)中非常常用的組件.
比如網(wǎng)絡(luò)通信中, 如果對方 500ms 內(nèi)沒有返回?cái)?shù)據(jù), 則斷開連接嘗試重連.
比如一個(gè) Map, 希望里面的某個(gè) key 在 3s 之后過期(自動刪除).
類似于這樣的場景就需要用到定時(shí)器.
??Java標(biāo)準(zhǔn)庫中的定時(shí)器
-
標(biāo)準(zhǔn)庫中提供了一個(gè) Timer 類. Timer 類的核心方法為 schedule .
-
schedule 包含兩個(gè)參數(shù).
-
第一個(gè)參數(shù)指定即將要執(zhí)行的任務(wù)代碼,
-
第二個(gè)參數(shù)指定多長時(shí)間之后執(zhí)行 (單位為毫秒)
代碼示例:
下面程序分別有一個(gè)定時(shí)器,設(shè)置了三個(gè)不同的時(shí)間
import java.util.Timer;
import java.util.TimerTask;
public class TestDemo {
public static void main(String[] args) {
Timer timer = new Timer();
System.out.println("程序啟動!");
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("定時(shí)器3");
}
},3000);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("定時(shí)器2");
}
},2000);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("定時(shí)器1");
}
},1000);
}
}
運(yùn)行結(jié)果如下:
結(jié)果如我們所示,按照時(shí)間順序進(jìn)行打印
??模擬實(shí)現(xiàn)定時(shí)器
首先我們先來看一下定時(shí)器的構(gòu)成
??定時(shí)器的構(gòu)成
- 一個(gè)帶優(yōu)先級的阻塞隊(duì)列
- 為啥要帶優(yōu)先級呢?
因?yàn)樽枞?duì)列中的任務(wù)都有各自的執(zhí)行時(shí)刻 (delay). 最先執(zhí)行的任務(wù)一定是 delay 最小的. 使用帶優(yōu)先級的隊(duì)列就可以高效的把這個(gè) delay 最小的任務(wù)找出來.
-
隊(duì)列中的每個(gè)元素是一個(gè) MyTask 對象.
-
MyTask 中帶有一個(gè)時(shí)間屬性, 隊(duì)首元素就是即將要執(zhí)行的任務(wù)
-
同時(shí)有一個(gè)線程一直掃描隊(duì)首元素, 看隊(duì)首元素是否需要執(zhí)行
??第一步:MyStack類的建立
包含兩個(gè)屬性
-
包含一個(gè) Runnable 對象
-
一個(gè) time(毫秒時(shí)間戳)
由于我們的MyTask類需要放入一個(gè)帶優(yōu)先級的阻塞隊(duì)列中,所以我們需要MyTack可以比較,這里博主選擇重寫 Comparable 接口里的compareTo方法
代碼實(shí)現(xiàn)如下:
public class MyTask implements Comparable<MyTask> {
private Runnable runnable;
private long time;
public MyTask() {
System.out.println(1);
}
public void tad() {
System.out.println(2);
}
public MyTask(Runnable runnable, long time) {
this.runnable = runnable;
this.time = time;
}
public long gettime(MyTask) {
return this.time;
}
//執(zhí)行任務(wù)
public void run() {
runnable.run();
}
@Override
public int compareTo(MyTask o) {
return (int)(this.time - o.time);
}
}
??第二步:創(chuàng)建MyTimer類
該類需要有一個(gè)帶有優(yōu)先級的阻塞隊(duì)列
還需要有一個(gè)可schedule 方法用于我們來插入我們我們需要執(zhí)行的任務(wù)
public class MyTimer {
// 有一個(gè)阻塞優(yōu)先級隊(duì)列, 來保存任務(wù).
private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();
// 指定兩個(gè)參數(shù)
// 第一個(gè)參數(shù)是 任務(wù) 內(nèi)容
// 第二個(gè)參數(shù)是 任務(wù) 在多少毫秒之后執(zhí)行. 形如 1000
public void schedule(Runnable runnable, long after) {
// 注意這里的時(shí)間上的換算
MyTask task = new MyTask(runnable, System.currentTimeMillis() + after);
queue.put(task);
}
}
-
由于我們輸入的都是一個(gè)時(shí)間大小,所以我們需要進(jìn)行處理一下,
-
這里的System.currentTimeMillis()是獲取當(dāng)時(shí)的時(shí)間戳
-
再加上所需要的時(shí)間大小即達(dá)到我們效果
其次還需要一個(gè)線程循環(huán)掃描
-
該掃描我們需要做的是
-
取出隊(duì)首元素, 檢查看看隊(duì)首元素任務(wù)是否到時(shí)間了.
-
如果時(shí)間沒到, 就把任務(wù)塞回隊(duì)列里去.
-
如果時(shí)間到了, 就把任務(wù)進(jìn)行執(zhí)行.
private Thread t = null;
public MyTimer() {
t = new Thread() {
@Override
public void run() {
while (true) {
try {
// 取出隊(duì)首元素, 檢查看看隊(duì)首元素任務(wù)是否到時(shí)間了.
// 如果時(shí)間沒到, 就把任務(wù)塞回隊(duì)列里去.
// 如果時(shí)間到了, 就把任務(wù)進(jìn)行執(zhí)行.
MyTask myTask = queue.take();
long curTime = System.currentTimeMillis();
if (curTime < myTask.getTime()) {
// 還沒到點(diǎn), 先不必執(zhí)行
// 現(xiàn)在是 13:00, 取出來的任務(wù)是 14:00 執(zhí)行
//塞回去
queue.put(myTask);
} else {
// 時(shí)間到了!! 執(zhí)行任務(wù)!!
myTask.run();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
//啟動線程
t.start();
}
??第三步:解決相關(guān)問題
- 問題一:while (true) 轉(zhuǎn)的太快了, 造成了無意義的 CPU 浪費(fèi).
比如第一個(gè)任務(wù)設(shè)定的是 1 min 之后執(zhí)行某個(gè)邏輯. 但是這里的 while (true) 會導(dǎo)致每秒鐘訪問隊(duì)首元素幾萬次.
解決辦法:引入一個(gè)locker對象, 借助該對象的 wait / notify 來解決 while (true) 的忙等問題.
我們在循環(huán)掃描里:引入 wait, 等待一定的時(shí)間.并修改 MyTimer 的 schedule 方法, 每次有新任務(wù)到來的時(shí)候喚醒一下循環(huán)掃描線程. (因?yàn)樾虏迦氲娜蝿?wù)可能是需要馬上執(zhí)行的)
- 問題二:原子性問題
由于我們的出隊(duì)列操作和判斷語句不具有原子性
問題情況如下:
出隊(duì)列操作拿到任務(wù)后,還沒有進(jìn)行判斷
然后這時(shí)候有一個(gè)來了一個(gè)新任務(wù)
但是此時(shí)我們該任務(wù)還沒有wait()操作,而且我們由于添加新元素,notify()操作已執(zhí)行,這就導(dǎo)致后面的wait操作不會被喚醒,那么新來的任務(wù)就在相應(yīng)時(shí)間來沒有被執(zhí)行
解決方法:將出隊(duì)列操作與判斷操作都加上鎖
代碼實(shí)現(xiàn)如下:
import java.util.concurrent.PriorityBlockingQueue;
public class MyTimer {
// 有一個(gè)阻塞優(yōu)先級隊(duì)列, 來保存任務(wù).
private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();
// 掃描線程
private Thread t = null;
private Object locker = new Object();
public MyTimer() {
t = new Thread() {
@Override
public void run() {
while (true) {
try {
// 取出隊(duì)首元素, 檢查看看隊(duì)首元素任務(wù)是否到時(shí)間了.
// 如果時(shí)間沒到, 就把任務(wù)塞回隊(duì)列里去.
// 如果時(shí)間到了, 就把任務(wù)進(jìn)行執(zhí)行.
synchronized (locker) {
MyTask myTask = queue.take();
long curTime = System.currentTimeMillis();
if (curTime < myTask.getTime()) {
// 還沒到點(diǎn), 先不必執(zhí)行
// 現(xiàn)在是 13:00, 取出來的任務(wù)是 14:00 執(zhí)行
queue.put(myTask);
// 在 put 之后, 進(jìn)行一個(gè) wait
locker.wait(myTask.getTime() - curTime);
} else {
// 時(shí)間到了!! 執(zhí)行任務(wù)!!
myTask.run();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
t.start();
}
// 指定兩個(gè)參數(shù)
// 第一個(gè)參數(shù)是 任務(wù) 內(nèi)容
// 第二個(gè)參數(shù)是 任務(wù) 在多少毫秒之后執(zhí)行. 形如 1000
public void schedule(Runnable runnable, long after) {
// 注意這里的時(shí)間上的換算
MyTask task = new MyTask(runnable, System.currentTimeMillis() + after);
queue.put(task);
synchronized (locker) {
locker.notify();
}
}
}
??完整代碼實(shí)現(xiàn)與測試
計(jì)時(shí)器完整代碼:
import java.util.concurrent.PriorityBlockingQueue;
class MyTask implements Comparable<MyTask> {
private Runnable runnable;
private long time;
public MyTask() {
System.out.println(1);
}
public void tad() {
System.out.println(2);
}
public MyTask(Runnable runnable, long time) {
this.runnable = runnable;
this.time = time;
}
public long getTime() {
return this.time;
}
//執(zhí)行任務(wù)
public void run() {
runnable.run();
}
@Override
public int compareTo(MyTask o) {
return (int)(this.time - o.time);
}
}
public class MyTimer {
// 有一個(gè)阻塞優(yōu)先級隊(duì)列, 來保存任務(wù).
private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();
// 掃描線程
private Thread t = null;
private Object locker = new Object();
public MyTimer() {
t = new Thread() {
@Override
public void run() {
while (true) {
try {
// 取出隊(duì)首元素, 檢查看看隊(duì)首元素任務(wù)是否到時(shí)間了.
// 如果時(shí)間沒到, 就把任務(wù)塞回隊(duì)列里去.
// 如果時(shí)間到了, 就把任務(wù)進(jìn)行執(zhí)行.
synchronized (locker) {
MyTask myTask = queue.take();
long curTime = System.currentTimeMillis();
if (curTime < myTask.getTime()) {
// 還沒到點(diǎn), 先不必執(zhí)行
// 現(xiàn)在是 13:00, 取出來的任務(wù)是 14:00 執(zhí)行
queue.put(myTask);
// 在 put 之后, 進(jìn)行一個(gè) wait
locker.wait(myTask.getTime() - curTime);
} else {
// 時(shí)間到了!! 執(zhí)行任務(wù)!!
myTask.run();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
t.start();
}
// 指定兩個(gè)參數(shù)
// 第一個(gè)參數(shù)是 任務(wù) 內(nèi)容
// 第二個(gè)參數(shù)是 任務(wù) 在多少毫秒之后執(zhí)行. 形如 1000
public void schedule(Runnable runnable, long after) {
// 注意這里的時(shí)間上的換算
MyTask task = new MyTask(runnable, System.currentTimeMillis() + after);
queue.put(task);
synchronized (locker) {
locker.notify();
}
}
}
測試代碼如下
public class TestDemo2 {
public static void main(String[] args) {
MyTimer myTimer = new MyTimer();
System.out.println("程序啟動");
myTimer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("計(jì)時(shí)器3");
}
},3000);
myTimer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("計(jì)時(shí)器2");
}
},2000);
myTimer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("計(jì)時(shí)器1");
}
},1000);
}
}
測試結(jié)果如下:文章來源:http://www.zghlxwxcb.cn/news/detail-736722.html
?總結(jié)
關(guān)于《【JavaEE初階】 定時(shí)器詳解與實(shí)現(xiàn)》就講解到這兒,感謝大家的支持,歡迎各位留言交流以及批評指正,如果文章對您有幫助或者覺得作者寫的還不錯(cuò)可以點(diǎn)一下關(guān)注,點(diǎn)贊,收藏支持一下!文章來源地址http://www.zghlxwxcb.cn/news/detail-736722.html
到了這里,關(guān)于【JavaEE初階】 定時(shí)器詳解與實(shí)現(xiàn)的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!