Java定時任務Timer調度器【一】 源碼分析(圖文詳解版)

就以鬧鐘的例子開頭吧(後續小節皆以鬧鐘爲例,全部源代碼只列關鍵部分)。java

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); //模擬鬧鐘執行時間,省略異常。。。
        }
    }
}

一秒之後鬧鐘每隔兩秒執行一次。ide

[main]    開啓鬧鐘調度!
[Timer-0] 鬧鐘1:嘀。。。
[Timer-0] 鬧鐘1:嘀。。。
[Timer-0] 鬧鐘1:嘀。。。

從打印結果能夠看到,鬧鐘調度與執行並不是一線程。oop

下面是Timer時序圖,能夠了解Timer的大概流程。源碼分析

下面開始分析Timer源碼。this

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的執行過程。spa

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)。code

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的運行場景。get

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

如此周而復始。。

相關文章
相關標籤/搜索