Java中的定時器Timer詳解
簡單來說,定時器就相當(dāng)于一個“鬧鐘”,給定時器設(shè)定一個任務(wù),約定這個任務(wù)在xxx時間之后執(zhí)行~
Timer類提供了一個核心接口,schedule(安排) 指定一個任務(wù)交給定時器,在一定時間之后再去執(zhí)行這個任務(wù)~
如何實現(xiàn)定時器的效果~
- Timer中要包含一個Task類,每個Task就表示一個具體的任務(wù)實例,Task里面包含一個時間戳(啥時候執(zhí)行這個任務(wù)),還包含一個Runnable實例(用來表示任務(wù)具體是啥)。
- Timer里面通過一個帶優(yōu)先級的阻塞隊列,來組織如干個task。
- 這里的優(yōu)先級是按照時間的先后來排優(yōu)先級,快要到時間的任務(wù)優(yōu)先級更高。
- 也就是 給 堆 加上 wait/notify ,讓它能夠帶阻塞效果。
- 標準庫里提供這樣的隊列PriorityBlockingQueue
- Timer 中還需要一個專門的線程,讓這個線程不停的掃描隊首元素,看看隊首元素是不是已經(jīng)可以執(zhí)行了,如果可以執(zhí)行了,就執(zhí)行,反之繼續(xù)在隊列中等待。
具體如何用代碼實現(xiàn)這樣一個定時器Timer:
一般去設(shè)定時間的時候,傳入的時間,都是一個時間間隔
例如:傳入1000 ,就代表從當(dāng)前開始過1000ms之后在執(zhí)行;
而我這里為了后面代碼方便判斷,在這里記錄一下絕對時間,這樣this.time里就是一個標志的ms級時間戳了,后續(xù)只需要獲取當(dāng)前時間戳在和這里的time對比一下就好了。

Task 要放到一個優(yōu)先級隊列中,但是優(yōu)先級隊列里面是需要比較優(yōu)先級的,所以可以讓Task類實現(xiàn)Comparable接口,重寫compareTo方法來進行比較。

這里希望時間較小的排在見面。

這里獲取一下當(dāng)前時間currTime,如果當(dāng)前時間大于等于task里約定的時間,超過說明時間到,執(zhí)行任務(wù),反之沒到,把取去的
任務(wù)再放回隊列,繼續(xù)等待。

這里還涉及到一個問題:
舉個例子:假如你早上定了一個8.30的 鬧鐘,8點的時候你醒了,看了下時間,還沒到,你就繼續(xù)睡,
但是這里是while(true),就意味著每過一秒鐘就要看一次鬧鐘,8.01看一次,8.02看一次,這樣就會白白的浪費一些資源,這就出現(xiàn)了“盲等”,在等待任務(wù)時間的過程中,一直持有著CPU資源~
所以這里就需要優(yōu)化一下:使用wait/notify機制,就可很好的改善盲等問題~
如果取出任務(wù)發(fā)現(xiàn)還沒到時間,就wait,等待一定時間,這里使用的wait()的重載版本,wait()里寫一個參數(shù),達到等待時間,自動醒過來~ 此時就大大降低了掃描次數(shù)和成本,

這里的notify(),就是保證當(dāng)線程中如果有線程在WAITING狀態(tài)的線程,就需要顯示的喚醒一下線程。
舉個例子;
如果隊首元素8.30在執(zhí)行,等待30分鐘,但是此時,可能突然插入一個任務(wù),讓你8.10的時候去干一件事,如果你8.30再去喚醒的話,8.10的任務(wù)就來不及了!
所以每次插入新任務(wù)的時候,都喚醒一下woeker線程,讓worker線程重新獲取一下隊首元素,看看接下來的任務(wù)等待多少時間合適。

//簡單定時器
public class TestTimer {
//每個 Task 實例 就包含一個要執(zhí)行的任務(wù)
//Task 要放到一個優(yōu)先級隊列中,但是優(yōu)先級隊列里面是需要比較優(yōu)先級的
static class Task implements Comparable<Task>{
//什么時候執(zhí)行
private long time;
//執(zhí)行什么任務(wù)
private Runnable command;
public Task(Runnable command ,long time){
this.command = command;
this.time = System.currentTimeMillis()+time;
}
public void run(){
//執(zhí)行Runable 里面的run方法
command.run();
}
@Override
public int compareTo(Task o) {
return (int)(this.time - o.time);
}
}
static class Timer{
//先創(chuàng)建一個帶優(yōu)先級的阻塞隊列
private PriorityBlockingQueue<Task> queue = new PriorityBlockingQueue<>();
//用這個對象來完成線程之間的協(xié)調(diào)任務(wù)
private Object meilbox = new Object();
//schedule 方法就是把一個Task 放在Timer中
public void schedule(Runnable command,long after){
Task task = new Task(command,after);
//將當(dāng)前任務(wù)放入對列
queue.put(task);
//當(dāng)worker 線程中包含wait機制的時候,在安排任務(wù)的時候就需要顯示的喚醒一下
synchronized (meilbox){
meilbox.notify();
}
}
//寫一個構(gòu)造方法,創(chuàng)建線程
public Timer(){
//創(chuàng)建一個線程,讓這個線程去掃描隊列的隊首元素,看看能不能執(zhí)行
Thread worker = new Thread(){
@Override
public void run() {
//取出隊首元素,判斷這個元素能不能執(zhí)行
while(true){
try {
Task task = queue.take();
long currTime = System.currentTimeMillis();
if(currTime >= task.time){
//時間到,執(zhí)行任務(wù)
task.run();
}else{
//時間沒到,繼續(xù)等待
queue.put(task);
synchronized (meilbox){
meilbox.wait(task.time-currTime);
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
worker.start();
}
}
}
測試:
public static void main(String[] args) {
Timer timer = new Timer();
Runnable command = new Runnable() {
@Override
public void run() {
System.out.println("時間到了~");
// timer.schedule(this,3000); 每隔3是就執(zhí)行一次
}
};
System.out.println("安排任務(wù)");
timer.schedule(command,3000);
}
}
安排任務(wù)后,等待3s就可以執(zhí)行了

這里補充一下Timer原生類中的一些方法
- schedule(TimerTask task, Date time) 在指定的日期執(zhí)行一次TimerTask任務(wù);如果日期time早于當(dāng)前時間,則立刻執(zhí)行。
- schedule(TimerTask task, long delay, long period) 以當(dāng)前時間為基準,延遲指定的毫秒后,再按指定的時間間隔地?zé)o限次數(shù)的執(zhí)行TimerTask任務(wù)。
- schedule(TimerTask task, Date firstTime, long period) 在指定的日期之后,按指定的時間間隔地?zé)o限次數(shù)的執(zhí)行TimerTask任務(wù)。
- scheduleAtFixedRate(TimerTask task, long delay, long period) 以當(dāng)前時間為基準,延遲指定的毫秒后,再按指定的時間間隔周期性地?zé)o限次數(shù)的執(zhí)行TimerTask任務(wù)。
總結(jié)
本篇文章就到這里了,希望能給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
如何通過properties文件配置web.xml中的參數(shù)
這篇文章主要介紹了如何通過properties文件配置web.xml中的參數(shù)方法,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-08-08
在Spring Boot中使用Spark Streaming進行實時數(shù)據(jù)處理和流式計算的步驟
這篇文章主要介紹了在Spring Boot中使用Spark Streaming進行實時數(shù)據(jù)處理和流式計算,通過本文的介紹,我們了解了在Spring Boot中使用Spark Streaming進行實時數(shù)據(jù)處理和流式計算的詳細步驟,需要的朋友可以參考下2024-03-03
java構(gòu)造函數(shù)示例(構(gòu)造方法)
這篇文章主要介紹了java構(gòu)造函數(shù)示例(構(gòu)造方法),需要的朋友可以參考下2014-03-03

