面試必問!JDK 中定時(shí)器是如何實(shí)現(xiàn)的?
閱讀本文大概需要 6 分鐘。
作者:Fooisart
來源:https://www.jianshu.com/p/e21eb60a2c41
jdk中能夠?qū)崿F(xiàn)定時(shí)器功能的大致有三種方式:
java.util.Timer
java.util.concurrent.DelayQueue
java.util.concurrent.ScheduledThreadPoolExecutor
一. java.util.Timer
/**
* 安排指定的任務(wù)task在指定的時(shí)間firstTime開始進(jìn)行重復(fù)的固定速率period執(zhí)行
* 每天中午12點(diǎn)都執(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);//控制小時(shí)
calendar.set(Calendar.MINUTE, 0);//控制分鐘
calendar.set(Calendar.SECOND, 0);//控制秒
Date time = calendar.getTime();//執(zhí)行任務(wù)時(shí)間為12:00:00
//每天定時(shí)12:00執(zhí)行操作,每隔2秒執(zhí)行一次
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println(new Date() + "執(zhí)行任務(wù)。。。");
}
}, time, 1000 * 2);
}
}
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í)行的時(shí)間
period 表示每次間隔時(shí)間
private void sched(TimerTask task, long time, long period) { //省略非重點(diǎn)代碼 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中。
/** * 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); }
public Timer(String name) { thread.setName(name); thread.start(); }
public void run() { try { mainLoop(); } finally { // Someone killed this Thread, behave as if Timer cancelled synchronized(queue) { newTasksMayBeScheduled = false; queue.clear(); // Eliminate obsolete references } } }
/**
* 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 (taskFired = (executionTime<=currentTime)),表示已經(jīng)到了執(zhí)行時(shí)間,那么下面執(zhí)行任務(wù)就好了;
if (!taskFired),表示未到執(zhí)行時(shí)間,那么等待就好了。那么是如何等待的呢?再仔細(xì)一看,原來是調(diào)用了Object.wait(long timeout)。
java.util.concurrent.DelayQueue
先上示例代碼:
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(); } }
構(gòu)造DelayTask,其中的延遲時(shí)間是5秒
將任務(wù)放入隊(duì)列
從隊(duì)列中取任務(wù)
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(); } }
第一次是當(dāng)隊(duì)列為空時(shí),等待;
第二次等待是因?yàn)椋l(fā)現(xiàn)有任務(wù),沒有到執(zhí)行時(shí)間,并且有準(zhǔn)備執(zhí)行的線程(leader)。咱們得講理吧,既然已經(jīng)有人在準(zhǔn)備執(zhí)行了,咱們就得等吧。
第三次是真正延時(shí)的地方了,available.awaitNanos(delay),此時(shí)也沒有別的線程要執(zhí)行,也就是我將要執(zhí)行,所有等待剩下的延遲時(shí)間即可。
java.util.concurrent.ScheduledThreadPoolExecutor
使用Object.wait()
使用Conditon.await()
使用Thread.sleep()可以實(shí)現(xiàn)嘛?如果可以,為何不用呢?
Object.wait()與Conditon.await()有何異同?
<END>
掃碼加入技術(shù)交流群,不定時(shí)「送書」
推薦閱讀:
MyBatis 批量插入的 3 種方式!還有誰不會(huì)?
最近面試BAT,整理一份面試資料《Java面試BATJ通關(guān)手冊(cè)》,覆蓋了Java核心技術(shù)、JVM、Java并發(fā)、SSM、微服務(wù)、數(shù)據(jù)庫、數(shù)據(jù)結(jié)構(gòu)等等。
朕已閱 

