Java定時任務Timer排程器【一】 原始碼分析(圖文詳解版)
阿新 • • 發佈:2018-11-25
就以鬧鐘的例子開頭吧(後續小節皆以鬧鐘為例,所有原始碼只列關鍵部分)。
public class ScheduleDemo { public static void main(String[] args) throws InterruptedException { long delay = 1000; // 一秒後開始執行 long period = 2000; // 執行間隔 Timer timer = new Timer(); AlarmTask alarm = new AlarmTask("鬧鐘1"); log.info("["+Thread.currentThread().getName()+"]開啟鬧鐘排程!"); timer.schedule(alarm,delay,period); } /** * 模擬鬧鐘 */ static class AlarmTask extends TimerTask{ String name ; public AlarmTask(String name){ this.name=name; } @Override public void run() { log.info("["+Thread.currentThread().getName()+"]"+name+":嘀。。。"); Thread.sleep(1000); //模擬鬧鐘執行時間,省略異常。。。 } } }
一秒以後鬧鐘每隔兩秒執行一次。
[main] 開啟鬧鐘排程!
[Timer-0] 鬧鐘1:嘀。。。
[Timer-0] 鬧鐘1:嘀。。。
[Timer-0] 鬧鐘1:嘀。。。
從列印結果可以看到,鬧鐘排程與執行並非一執行緒。
下面是Timer時序圖,可以瞭解Timer的大概流程。
下面開始分析Timer原始碼。
public class Timer { private final TaskQueue queue = new TaskQueue(); private final TimerThread thread = new TimerThread(queue); public Timer() { this("Timer-" + serialNumber()); } public Timer(String name) { thread.setName(name); thread.start(); }
可以看到,Timer中維護了一個內部執行緒與佇列,且在例項化Timer的同時,就已初始化好了。在初始化Timer時,內部執行緒TimerThread開始啟動,下面是TimerThread的執行過程。
class TimerThread extends Thread { public void run() { mainLoop(); } private void mainLoop() { while (true) { synchronized(queue) { // 佇列為空,執行緒被阻塞 while (queue.isEmpty() && newTasksMayBeScheduled) queue.wait();
可以看到,雖然執行緒TimerThread已啟動,但因佇列為空,執行緒被阻塞(等待queue鎖)。
以上是Timer timer = new Timer()的整個執行過程,繼續看timer.schedule(alarm,delay,period)。
public class Timer {
public void schedule(TimerTask task, long delay, long period) {
sched(task, System.currentTimeMillis()+delay, -period);
}
private void sched(TimerTask task, long time, long period) {
synchronized(queue) {
synchronized(task.lock) {
task.nextExecutionTime = time;
task.period = period;
task.state = TimerTask.SCHEDULED;
}
// 將鬧鐘加入佇列
queue.add(task);
// 此時正好滿足條件,主執行緒釋放queue鎖,並喚醒TimerThread
if (queue.getMin() == task)
queue.notify();
}
}
從原始碼可看出,主執行緒正好滿足queue.getMin() == task,此時將喚醒TimerThread執行緒(waiting)並釋放queue鎖。
下面再切換到TimerThread的執行場景。
private void mainLoop() {
while (true) {
synchronized(queue) {
while (queue.isEmpty() && newTasksMayBeScheduled)
queue.wait();
if (queue.isEmpty())
break;
task = queue.getMin();
synchronized(task.lock) {
currentTime = System.currentTimeMillis();
executionTime = task.nextExecutionTime;
// 已經到了執行時間
if (taskFired = (executionTime<=currentTime)) {
// ...重新定義下次鬧鐘執行時間
}
}
if (!taskFired)
// 執行時間未到,執行緒再次阻塞
queue.wait(executionTime - currentTime);
}
if (taskFired)
task.run(); // 同步執行使用者定義的鬧鐘
}
}
通過上面的原始碼分析,TimerThread被喚醒後,將判斷執行時間,時間到則初始化下次鬧鐘的執行時間並執行本次鬧鐘,否則執行緒將等待指定時間。
如此周而復始。。