面試必問!JDK 中定時器是如何實現(xiàn)的?
jdk中能夠?qū)崿F(xiàn)定時器功能的大致有三種方式:
java.util.Timer
java.util.concurrent.DelayQueue
java.util.concurrent.ScheduledThreadPoolExecutor
靜下心來,咱們一一探究。
一. java.util.Timer
示例代碼:
/**
* 安排指定的任務(wù)task在指定的時間firstTime開始進行重復(fù)的固定速率period執(zhí)行
* 每天中午12點都執(zhí)行一次
*
* @author Fooisart
* Created on 21:46 14-01-2019
*/
public class TimerDemo {
public static void main(String[] args) {
Timer timer = new Timer();
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.HOUR_OF_DAY, 12);//控制小時
calendar.set(Calendar.MINUTE, 0);//控制分鐘
calendar.set(Calendar.SECOND, 0);//控制秒
Date time = calendar.getTime();//執(zhí)行任務(wù)時間為12:00:00
//每天定時12:00執(zhí)行操作,每隔2秒執(zhí)行一次
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println(new Date() + "執(zhí)行任務(wù)。。。");
}
}, time, 1000 * 2);
}
}
Demo中使用了Timer實現(xiàn)了一個定時任務(wù),該任務(wù)在每天12點開始執(zhí)行,并且每隔2秒執(zhí)行一次。
順手牽羊:查看源碼時,無意發(fā)現(xiàn)Timer中有schedule與scheduleAtFixedRate,它倆都可以到約定時間按照指定時間間隔執(zhí)行。然而它倆的區(qū)別是什么呢?官方解釋:一個是Fixed-delay,一個是Fixed-rate。那么這兩個詞到底是什么意思呢?把demo中的代碼運行一遍,然后把schedule換成scheduleAtFixedRate,就全部了然了。
示例代碼中較為簡潔,能看出控制執(zhí)行時間的方法應(yīng)該是 timer.schedule(),跟進去看源碼:
public void schedule(TimerTask task, Date firstTime, long period) { if (period <= 0) throw new IllegalArgumentException("Non-positive period."); sched(task, firstTime.getTime(), -period); }
task 表示要執(zhí)行的任務(wù)邏輯
firstTime 表示第一次執(zhí)行的時間
period 表示每次間隔時間
繼續(xù)跟進:
private void sched(TimerTask task, long time, long period) { //省略非重點代碼 synchronized(queue) { if (!thread.newTasksMayBeScheduled) throw new IllegalStateException("Timer already cancelled."); synchronized(task.lock) { if (task.state != TimerTask.VIRGIN) throw new IllegalStateException( "Task already scheduled or cancelled"); task.nextExecutionTime = time; task.period = period; task.state = TimerTask.SCHEDULED; } queue.add(task); if (queue.getMin() == task) queue.notify(); } }
這里其實做了兩個事情
給task設(shè)定了一些參數(shù),類似于初始化task。這里還給它加了把鎖,可以思考一下為甚要在此初始化?為何要加鎖?(不是本文范疇,各位伙伴自行思考)
把初始化后的task加入到queue中。
讀到這里,我們還是沒有看到到底是如何實現(xiàn)定時的?別著急,繼續(xù)。進入queu.add(task)
/** * Adds a new task to the priority queue. */ void add(TimerTask task) { // Grow backing store if necessary if (size + 1 == queue.length) queue = Arrays.copyOf(queue, 2*queue.length); queue[++size] = task; fixUp(size); }
這里注釋提到,加入一個新任務(wù)到優(yōu)先級隊列中去。其實這里的TimerTask[]是一個優(yōu)先級隊列,使用數(shù)組存儲方式。并且它的數(shù)據(jù)結(jié)構(gòu)是heap。包括從fixUp()我們也能看出來,它是在保持堆屬性,即堆化(heapify)。
那么能分析的都分析完了,還是沒能看到定時是如何實現(xiàn)的?再次靜下來想一想,定時任務(wù)如果想執(zhí)行,首先得啟動定時器。所有咱們再次關(guān)注構(gòu)造方法。
Timer一共有4個構(gòu)造方法,看最底層的:
public Timer(String name) { thread.setName(name); thread.start(); }
可以看到,這里在啟動一個thread,那么既然是一個Thread,那肯定就得關(guān)注它的 run()方法了。進入:
public void run() { try { mainLoop(); } finally { // Someone killed this Thread, behave as if Timer cancelled synchronized(queue) { newTasksMayBeScheduled = false; queue.clear(); // Eliminate obsolete references } } }
繼續(xù)進入mainLoop():
/**
* The main timer loop. (See class comment.)
*/
private void mainLoop() {
while (true) {
try {
TimerTask task;
boolean taskFired;
synchronized(queue) {
//省略
long currentTime, executionTime;
task = queue.getMin();
synchronized(task.lock) {
if (task.state == TimerTask.CANCELLED) {
queue.removeMin();
continue; // No action required, poll queue again
}
currentTime = System.currentTimeMillis();
executionTime = task.nextExecutionTime;
if (taskFired = (executionTime<=currentTime)) {
if (task.period == 0) { // Non-repeating, remove
queue.removeMin();
task.state = TimerTask.EXECUTED;
} else { // Repeating task, reschedule
queue.rescheduleMin(
task.period<0 ? currentTime - task.period
: executionTime + task.period);
}
}
}
if (!taskFired) // Task hasn't yet fired; wait
queue.wait(executionTime - currentTime);
}
if (taskFired) // Task fired; run it, holding no locks
task.run();
} catch(InterruptedException e) {
}
}
}
從上述源碼中,可以看出有兩個重要的if
if (taskFired = (executionTime<=currentTime)),表示已經(jīng)到了執(zhí)行時間,那么下面執(zhí)行任務(wù)就好了;
if (!taskFired),表示未到執(zhí)行時間,那么等待就好了。那么是如何等待的呢?再仔細(xì)一看,原來是調(diào)用了Object.wait(long timeout)。
到這里我們知道了,原來jdk中的定時器是這樣實現(xiàn)的啊,等待是使用最簡單的Object.wait()實現(xiàn)的??!別著急,這里有個小提問:使用Therad.sleep()可以實現(xiàn)嘛?如果可以,為何不用呢?
java.util.concurrent.DelayQueue
比較細(xì)致地分析了java.util.Timer,DelayQueue也大同小異。整理一下心情,重新出發(fā)。
先上示例代碼:
DelayQueue它本質(zhì)上是一個隊列,而這個隊列里也只有存放Delayed的子類才有意義,所有定義了DelayTask:
public class DelayTask implements Delayed { private Date startDate = new Date(); public DelayTask(Long delayMillions) { this.startDate.setTime(new Date().getTime() + delayMillions); } @Override public int compareTo(Delayed o) { long result = this.getDelay(TimeUnit.NANOSECONDS) - o.getDelay(TimeUnit.NANOSECONDS); if (result < 0) { return -1; } else if (result > 0) { return 1; } else { return 0; } } @Override public long getDelay(TimeUnit unit) { Date now = new Date(); long diff = startDate.getTime() - now.getTime(); return unit.convert(diff, TimeUnit.MILLISECONDS); }} public static void main(String[] args) throws Exception { BlockingQueue<DelayTask> queue = new DelayQueue<>(); DelayTask delayTask = new DelayTask(1000 * 5L); queue.put(delayTask); while (queue.size()>0){ queue.take(); } }
看main方法,主要做了三件事:
構(gòu)造DelayTask,其中的延遲時間是5秒
將任務(wù)放入隊列
從隊列中取任務(wù)
DelayQueue跟剛才的Timer.TaskQueue是比較相似的,都是優(yōu)先級隊列,放入元素時,都得堆化(DelayQueue.put()如果元素滿了,會阻塞。自行研究)。重點看queue.take()。
public E take() throws InterruptedException { final ReentrantLock lock = this.lock; lock.lockInterruptibly(); try { for (;;) { E first = q.peek(); if (first == null) available.await(); else { long delay = first.getDelay(NANOSECONDS); if (delay <= 0) return q.poll(); first = null; // don't retain ref while waiting if (leader != null) available.await(); else { Thread thisThread = Thread.currentThread(); leader = thisThread; try { available.awaitNanos(delay); } finally { if (leader == thisThread) leader = null; } } } } } finally { if (leader == null && q.peek() != null) available.signal(); lock.unlock(); } }
源碼中出現(xiàn)了三次await字眼:
第一次是當(dāng)隊列為空時,等待;
第二次等待是因為,發(fā)現(xiàn)有任務(wù),沒有到執(zhí)行時間,并且有準(zhǔn)備執(zhí)行的線程(leader)。咱們得講理吧,既然已經(jīng)有人在準(zhǔn)備執(zhí)行了,咱們就得等吧。
第三次是真正延時的地方了,available.awaitNanos(delay),此時也沒有別的線程要執(zhí)行,也就是我將要執(zhí)行,所有等待剩下的延遲時間即可。
這里咱們明白了,DelayQueue的等待是通過Condition.await()來實現(xiàn)的。請注意,這里又有一個小問題了:Object.wait()與Conditon.await()有何異同?
java.util.concurrent.ScheduledThreadPoolExecutor
由于ScheduledThreadPoolExecutor涉及到的線程池(ThreadPoolExecutor)內(nèi)容較多,所有就不詳細(xì)分析了,也考慮到讀到這里,難免有些疲倦。直接簡述一下結(jié)論:在創(chuàng)建ScheduledThreadPoolExecutor時,線程池的工作隊列使用的是DelayedWorkQueue,它的take()方法,與DelayQueue.take()方法極其相似,也有三個等待。
至此,要結(jié)束了??偨Y(jié)一下,jdk中實現(xiàn)定時器一共有兩種方式:
使用Object.wait()
使用Conditon.await()
還記得文中的兩個小提問嘛:
使用Thread.sleep()可以實現(xiàn)嘛?如果可以,為何不用呢?
Object.wait()與Conditon.await()有何異同?
最近給大家找了 零基礎(chǔ)學(xué)小程序
資源,怎么領(lǐng)?。?/span>
掃二維碼為,加我微信,回復(fù):零基礎(chǔ)學(xué)小程序
注意,不要亂回復(fù) 沒錯,不是機器人 記得一定要等待,等待才有好東西
