定時(shí)器可以幫助我們監(jiān)管一部分的任務(wù),只需要在規(guī)定的時(shí)間內(nèi)自動(dòng)執(zhí)行,這樣一來(lái)就不需要耗費(fèi)過(guò)多的人力資源。下面,和大家分享一下關(guān)于Java中的定時(shí)器具體是怎么實(shí)現(xiàn)的文章。
一、定時(shí)器
定時(shí)器相當(dāng)于一個(gè)任務(wù)管理器。有些任務(wù)可能現(xiàn)在執(zhí)行, 有些任務(wù)可能過(guò)1個(gè)小時(shí),甚至很久才會(huì)執(zhí)行。定時(shí)器就是對(duì)這些任務(wù)進(jìn)行管理監(jiān)視, 如果一個(gè)任務(wù)執(zhí)行時(shí)間到了,定時(shí)器就會(huì)將這個(gè)任務(wù)執(zhí)行。 保證所有的任務(wù)都會(huì)在合適的時(shí)間執(zhí)行。
二、定時(shí)器的實(shí)現(xiàn)
對(duì)于定時(shí)器的實(shí)現(xiàn),我們可以劃分為3個(gè)部分。
1、 使用一個(gè)Task類描述每一個(gè)任務(wù)(里面包含任務(wù)的執(zhí)行方法, 定時(shí)時(shí)間)。
2、 使用優(yōu)先級(jí)隊(duì)列管理這些任務(wù)類。
2.1 我們都知道優(yōu)先級(jí)隊(duì)列底層實(shí)現(xiàn)是堆(以小根堆為例), 堆頂?shù)脑厥撬械脑氐淖钚≈怠?我們以任務(wù)的定時(shí)時(shí)間為比較原則構(gòu)建, 這樣就可以保證堆頂元素的任務(wù)執(zhí)行時(shí)間是最短的(這樣的實(shí)現(xiàn),我們需要在Task類內(nèi)部定義比較規(guī)則-即重寫(xiě)Comparable接口的CompareTo方法)。
2.2 當(dāng)一個(gè)任務(wù)執(zhí)行完畢, 就會(huì)從優(yōu)先級(jí)隊(duì)列取出poll掉, 然后內(nèi)部重新組織保證新的堆頂元素是定時(shí)時(shí)間最短的。
2.3 如果說(shuō)堆頂?shù)娜蝿?wù)定時(shí)時(shí)間還沒(méi)有到達(dá)(當(dāng)然后續(xù)的任務(wù)定時(shí)時(shí)間肯定會(huì)更長(zhǎng),不會(huì)被執(zhí)行)
3、使用一個(gè)線程循環(huán)掃描優(yōu)先級(jí)隊(duì)列, 相當(dāng)于一個(gè)監(jiān)控線程,循環(huán)判斷堆頂任務(wù)是否滿足執(zhí)行時(shí)間。
三、定時(shí)器的組成
1、制定任務(wù)類Task
Task類包含任務(wù)的 執(zhí)行方法 和 定時(shí)時(shí)間。
1.1 執(zhí)行方法我采用封裝Runnable中run方法實(shí)現(xiàn), 這樣做是為了后續(xù)添加任務(wù)時(shí)方便寫(xiě)執(zhí)行邏輯。
1.2 定時(shí)時(shí)間就是long類型的變量
1.3 制定比較規(guī)則, 后續(xù)優(yōu)先級(jí)隊(duì)列中存放的是Task對(duì)象(而在內(nèi)部構(gòu)建時(shí),需要比較兩個(gè)Task對(duì)象的),對(duì)于對(duì)象的比較, 我們以對(duì)象的定時(shí)時(shí)間為規(guī)則, 制定小根堆。
static class Task implements Comparable<Task>{
//Runnable類中有一個(gè)run方法, 通過(guò)這個(gè)方法實(shí)現(xiàn)任務(wù)的執(zhí)行
private Runnable command;
//time表示執(zhí)行的時(shí)間
private long time;
//構(gòu)造方法
public Task(Runnable command, long time) {
this.command = command;
this.time = System.currentTimeMillis() + time; //將時(shí)間轉(zhuǎn)化為絕對(duì)時(shí)間
}
//執(zhí)行任務(wù)的邏輯
public void run() {
command.run();
}
//定義比較方法 - 方便后續(xù)的優(yōu)先級(jí)隊(duì)列構(gòu)建
@Override
public int compareTo(Task o) {
return (int)(this.time - o.time);
}
}
2、監(jiān)管線程&定時(shí)器對(duì)象Timer
監(jiān)管線程Worker中包含優(yōu)先級(jí)隊(duì)列(小根堆)queue 和 循環(huán)監(jiān)管的流程。
Timer對(duì)象封裝了監(jiān)管線程Woker 和 任務(wù)的添加方法schedule()
關(guān)于監(jiān)管線程的優(yōu)化
2.1 循環(huán)監(jiān)控存在一個(gè)弊端,那就是一直循環(huán)判斷, 占用CPU資源。
(假如堆首任務(wù)的執(zhí)行是1小時(shí)后, 再次期間監(jiān)管線程會(huì)跑1小時(shí)循環(huán)判斷。)
解決方法: 可以通過(guò)線程阻塞和喚醒來(lái)解決。在下面代碼有詳細(xì)注釋和實(shí)現(xiàn)。
2.1.1 如果任務(wù)1小時(shí)后執(zhí)行, 我們讓監(jiān)管線程wait(1小時(shí)), 但在此期間如果有新的任務(wù)添加進(jìn)來(lái)(可能新的任務(wù)需要等30分鐘就可以執(zhí)行,堆首元素發(fā)生變化) ,這時(shí)需要喚醒監(jiān)管線程來(lái)重新判斷。(由于wait和notify方法不在用一個(gè)類中實(shí)現(xiàn), 我們通過(guò)一個(gè)Object(mailBox)來(lái)阻塞、喚醒)
//檢測(cè)線程, 繼承Thread類,重寫(xiě)內(nèi)部run方法,屬于線程的創(chuàng)建方法之一。
static class Worker extends Thread {
//優(yōu)先級(jí)隊(duì)列 - JUC包里面
private PriorityBlockingQueue<Task> queue = null;
//為了對(duì)監(jiān)管線程進(jìn)行阻塞和喚醒,采用同一對(duì)象
private Object mailBox = null;
//構(gòu)造函數(shù)
public Worker(PriorityBlockingQueue<Task> queue, Object mailBox) {
this.queue = queue;
this.mailBox = mailBox;
}
@Override
public void run() {
//實(shí)現(xiàn)具體的執(zhí)行邏輯
while(true) {
try {
//1、取優(yōu)先級(jí)隊(duì)列的隊(duì)首元素
Task task = queue.peek();
//2、比較隊(duì)首的元素的時(shí)間是否大于當(dāng)前時(shí)間
if(task == null) {
continue;
}
long curTime = System.currentTimeMillis();
if(task.time > curTime) {
//時(shí)間還沒(méi)有到, 由于取出了任務(wù), 需要重新放置回去
//優(yōu)化1: 空循環(huán)等待 - wait(time) 讓線程休眠time時(shí)間,然后在執(zhí)行
// 如果在等待期間有新的任務(wù)添加, 這個(gè)時(shí)候我們喚醒線程, 繼續(xù)判斷(因?yàn)榇嬖谛碌臅r(shí)間過(guò)短需要立即執(zhí)行)
// 這個(gè)只需要添加一個(gè)新任務(wù)時(shí), 喚醒即可
//優(yōu)化2: 訪問(wèn)隊(duì)首元素而不是取出, 防止無(wú)所謂的刪除、插入。(維護(hù)優(yōu)先級(jí)隊(duì)列是有消耗的)
long gapTime = task.time - curTime;
synchronized (mailBox) {
mailBox.wait(gapTime);
}
}
else {
//直接執(zhí)行
//如果執(zhí)行到了, 則會(huì)刪除頭部元素, 調(diào)用任務(wù)的執(zhí)行過(guò)程。
task = queue.take();
task.run();
}
}
catch(InterruptedException e) {
e.printStackTrace();
break;
}
}
}
}
//定時(shí)器簡(jiǎn)單實(shí)現(xiàn)
static class Timer {
//定時(shí)器的實(shí)現(xiàn)步驟
//1、用一個(gè)類描述任務(wù)
//2、用優(yōu)先級(jí)隊(duì)列管理這些任務(wù), 比較方法通過(guò)任務(wù)的制定時(shí)間,每次取隊(duì)首元素
// 隊(duì)首元素是執(zhí)行時(shí)間最近的
private PriorityBlockingQueue<Task> queue = new PriorityBlockingQueue<>();
//3、用一個(gè)線程來(lái)循環(huán)掃描當(dāng)前的阻塞隊(duì)列,判斷隊(duì)首的執(zhí)行時(shí)間, 如果執(zhí)行時(shí)間到了,那就執(zhí)行。
//4、創(chuàng)建一個(gè)Object對(duì)象,用于設(shè)置線程阻塞使用的, 存在線程阻塞, 添加任務(wù)時(shí)喚醒的操作
private Object mailBox = new Object();
//構(gòu)造函數(shù)
public Timer() {
//創(chuàng)建線程
Worker worker = new Worker(queue, mailBox);
worker.start();
}
//4、提供一個(gè)方法, 讓調(diào)用者能夠把任務(wù)安排起來(lái)
public void schedule(Runnable command, long time) {
Task task = new Task(command, time);
queue.put(task);
synchronized (mailBox) {
mailBox.notify();
}
}
}
3、測(cè)試代碼
其中添加了4個(gè)任務(wù), 分別是2s、5s、7s、10s后執(zhí)行。
public static void main(String[] args) {
Timer timer = new Timer();
timer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("郝夢(mèng)武一號(hào)任務(wù)執(zhí)行, 執(zhí)行代號(hào):閃電; 定時(shí)時(shí)間:2s");
}
}, 2000);
timer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("郝夢(mèng)武二號(hào)任務(wù)執(zhí)行, 執(zhí)行代號(hào):暴風(fēng); 定時(shí)時(shí)間:5s");
}
}, 5000);
timer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("郝夢(mèng)武三號(hào)任務(wù)執(zhí)行, 執(zhí)行代號(hào):狂風(fēng); 定時(shí)時(shí)間:7s");
}
}, 7000);
timer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("郝夢(mèng)武三號(hào)任務(wù)執(zhí)行, 執(zhí)行代號(hào):地震; 定時(shí)時(shí)間:10s");
}
}, 10000);
}
4、測(cè)試結(jié)果
以上就是關(guān)于Java中的定時(shí)器具體實(shí)現(xiàn)過(guò)程的全部?jī)?nèi)容,如果想要了解更多關(guān)于Java定時(shí)器的相關(guān)內(nèi)容,推薦您閱讀Java教程中的定時(shí)器相關(guān)文章,里面有更具體的實(shí)例展示和詳細(xì)內(nèi)容。