1. 程式人生 > >Java定時任務Timer排程器【一】 原始碼分析(圖文詳解版)

Java定時任務Timer排程器【一】 原始碼分析(圖文詳解版)

就以鬧鐘的例子開頭吧(後續小節皆以鬧鐘為例,所有原始碼只列關鍵部分)。

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被喚醒後,將判斷執行時間,時間到則初始化下次鬧鐘的執行時間並執行本次鬧鐘,否則執行緒將等待指定時間。

如此周而復始。。