天天看點

Timer和TimerTask源碼解讀(面試題:雙重定時器)Timer和TimerTask源碼解讀

Timer和TimerTask源碼解讀

Timer是一種定時器工具,用來在一個背景線程計劃執行指定任務。它可以計劃執行一個任務一次或反複多次。

TimerTask是一個實作了Runnable接口的抽象類,代表一個可以被Timer執行的任務。

Timer和TimerTask基本使用

使用Timer線程實作和計劃執行一個任務:

  1. 實作自定義的TimerTask的子類,run方法包含要執行的任務代碼。
  2. 執行個體化Timer類,建立計時器背景線程。
  3. 執行個體化任務對象 (new RemindTask()).
  4. 制定執行計劃。這下面案例中采用schedule方法,第一個參數是TimerTask對象,第二個參數表示開始執行前的延時時間(機關是milliseconds)。還有一種方法可以指定任務的執行時間,例如指定任務在11:01 p.m.執行:
案例:
public class Reminder {
    Timer timer;

    public Reminder(int seconds) {
        timer = new Timer();
        timer.schedule(new RemindTask(), seconds * 1000);
    }

    class RemindTask extends TimerTask {
        public void run() {
            System.out.println("Time's up!");
            timer.cancel(); 
        }
    }

    public static void main(String args[]) {
        System.out.println("About to schedule task.");
        new Reminder(5);
        System.out.println("Task scheduled.");
    }
}
           
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.HOUR_OF_DAY, 23);
calendar.set(Calendar.MINUTE, 1);
calendar.set(Calendar.SECOND, 0);
Date time = calendar.getTime();

timer = new Timer();
timer.schedule(new RemindTask(), time);
           

終止Timer線程

預設情況下,隻要一個程式的timer線程在運作,那麼這個程式就會保持運作。可以通過以下四種方法終止一個timer線程:
  1. 調用timer的cancle方法。你可以從程式的任何地方調用此方法,甚至在一個timer task的run方法裡。
  2. 讓timer線程成為一個daemon線程(可以在建立timer時使用new Timer(true)達到這個目地),這樣當程式隻有daemon線程的時候,它就會自動終止運作。
  3. 當timer相關的所有task執行完畢以後,删除所有此timer對象的引用(置成null),這樣timer線程也會終止。
  4. 調用System.exit方法,使整個程式(所有線程)終止。

TimerTask任務:

TimerTask的狀态有4種:

  1. VIRGIN:未使用的,即初始狀态
  2. SCHEDULED:任務被放到TaskQueue,但是還未執行
  3. EXECUTED:隻針對隻執行一次的TimerTask,表示已經被執行,或者正準備執行
  4. CANCELLED:被取消
public abstract class TimerTask implements Runnable {
    final Object lock = new Object();//鎖
    int state = VIRGIN;
    
    static final int VIRGIN = 0;//VIRGIN:未使用的,即初始狀态
    static final int SCHEDULED   = 1;//任務被放到TaskQueue,但是還未執行
    static final int EXECUTED    = 2;//隻針對隻執行一次的TimerTask,表示已經被執行,或者正準備執行
    static final int CANCELLED   = 3;//被取消

    long nextExecutionTime;//執行時間屬性 
    long period = 0;//間隔
    protected TimerTask() {
    }
    
    public abstract void run();

    public boolean cancel() {
        synchronized(lock) {
            boolean result = (state == SCHEDULED);
            state = CANCELLED;
            return result;
        }
    }

    public long scheduledExecutionTime() {
        synchronized(lock) {
            return (period < 0 ? nextExecutionTime + period
                               : nextExecutionTime - period);
        }
    }
}
           

Timer定時器:

​ Timer定時器的實作原理,是通過Object.wait(timeout),來進行的線程阻塞,timeout是根據下次執行實際和目前實際之差來計算。實際上,可以歸結為一個多線程協作問題。

TaskQueue:

TaskQueue中存放一些列将要執行的TimerTask,以數組的形式存放,下标約小(注:下标為0不處理,即使用的最小下标為1),則表明優先級越高。

private final TaskQueue queue = new TaskQueue();

class TaskQueue {
  	//面有一個 TimerTask 的數組,注釋中指出這個隊列是一個優先級隊列(平衡二叉堆)
  	//按照執行時間進行判斷,queue[0]是執行時間最早的任務,反之最晚
    private TimerTask[] queue = new TimerTask[128];
    private int size = 0;
    int size() {}
  	//加入任務的方法
    void add(TimerTask task) {
        // Grow backing store if necessary
        if (size + 1 == queue.length)
            queue = Arrays.copyOf(queue, 2*queue.length);//如果queue滿容就擴大兩倍
        queue[++size] = task;
      	//根據二叉堆特性調整
        fixUp(size);
    }
    TimerTask getMin() {return queue[1];}
    TimerTask get(int i) {return queue[i];}
  	//二叉堆删堆頂元素,堆頂放到堆末尾,size--
    void removeMin() {
      	queue[1] = queue[size];
        queue[size--] = null;  // Drop extra reference to prevent memory leak
        fixDown(1);}
    void quickRemove(int i) {
        assert i <= size;
        queue[i] = queue[size];
        queue[size--] = null;  // Drop extra ref to prevent memory leak
    }
    void rescheduleMin(long newTime) {queue[1].nextExecutionTime = newTime;fixDown(1);}
    boolean isEmpty() {...}
    void clear() {...}
    private void fixUp(int k) {...}
    private void fixDown(int k) {... }
    void heapify() {...}
}
           

TimerThread:

TimerThread為Thread的擴充類,會一直從TaskQueue中擷取下标為1的TimerTask進行執行。并根據該TimerTask是否需要重複執行來決定是否放回到TaskQueue中。

private final TimerThread thread = new TimerThread(queue);

class TimerThread extends Thread {
  	//還有沒有指向 Timer 對象的活躍引用,如果标記為false,且任務隊列裡沒有任務了,就說明可以停止 Timer 的運作
    boolean newTasksMayBeScheduled = true;

    private TaskQueue queue;

    TimerThread(TaskQueue queue) {
        this.queue = queue;
    }
		
    public void run() {
        try {
            mainLoop();//循環取任務執行
        } finally {
            // Someone killed this Thread, behave as if Timer cancelled
            synchronized(queue) {
                newTasksMayBeScheduled = false;
                queue.clear();  // Eliminate obsolete references
            }
        }
    }

    private void mainLoop() {
        while (true) {
            try {
                TimerTask task;
                boolean taskFired;
                synchronized(queue) {
                    while (queue.isEmpty() && newTasksMayBeScheduled)
                        queue.wait();等待任務進入
                    if (queue.isEmpty())
                        break; // Queue is empty and will forever remain; die
                    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) {
            }
        }
    }
}	
           

schedule()方法:

// delay 是采用系統時間加上delay的毫秒數來進行的		
		public void schedule(TimerTask task, Date firstTime, long period) {
        if (period <= 0)
            throw new IllegalArgumentException("Non-positive period.");
        sched(task, firstTime.getTime(), -period);
    }
		以固定速率執行,在delay之後開始
		public void schedule(TimerTask task, long delay, long period) {
        if (delay < 0)
            throw new IllegalArgumentException("Negative delay.");
        if (period <= 0)
            throw new IllegalArgumentException("Non-positive period.");
        sched(task, System.currentTimeMillis()+delay, -period);
    }

		public void schedule(TimerTask task, Date time) {
        sched(task, time.getTime(), 0);
    }

		public void schedule(TimerTask task, long delay) {
        if (delay < 0)
            throw new IllegalArgumentException("Negative delay.");
        sched(task, System.currentTimeMillis()+delay, 0);
    }
           

Timer和TimerTask雙重定時器:

使用定時器,間隔 4 秒執行一次,再間隔 2 秒執行一次

public class DoubleTimer extends TimerTask {
    int sum = 0;

    public static void main(String[] args) {
        Timer timer = new Timer();//設定一個定時器
        timer.schedule(new DoubleTimer(),2000);//2秒後執行TimerTest中的run方法
        while (true) {
            System.out.println(new Date().getSeconds());
            try {
                Thread.sleep(1000);//間隔一秒
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

    @Override
    public void run() {
        sum = (sum +1)%2;//結果01交替
        System.err.println("執行定時任務,隔兩秒四秒交替列印");
        new Timer().schedule(new DoubleTimer(), 2000+2000*sum);
    }
}