Java定時(shí)調(diào)度機(jī)制 - Timer

簡(jiǎn)介

在實(shí)現(xiàn)定時(shí)調(diào)度功能的時(shí)候,我們往往會(huì)借助于第三方類庫來完成茁肠,比如:quartz画髓、Spring Schedule等等。JDK從1.3版本開始腻格,就提供了基于Timer的定時(shí)調(diào)度功能画拾。在Timer中,任務(wù)的執(zhí)行是串行的菜职。這種特性在保證了線程安全的情況下青抛,往往帶來了一些嚴(yán)重的副作用,比如任務(wù)間相互影響酬核、任務(wù)執(zhí)行效率低下等問題蜜另。為了解決Timer的這些問題适室,JDK從1.5版本開始,提供了基于ScheduledExecutorService的定時(shí)調(diào)度功能举瑰。

本節(jié)我們主要分析Timer的功能捣辆。對(duì)于ScheduledExecutorService的功能,我們將新開一篇文章來講解此迅。

如何使用

Timer需要和TimerTask配合使用汽畴,才能完成調(diào)度功能嗡官。Timer表示調(diào)度器葱蝗,TimerTask表示調(diào)度器執(zhí)行的任務(wù)。任務(wù)的調(diào)度分為兩種:一次性調(diào)度和循環(huán)調(diào)度轰异。下面坎怪,我們通過一些例子來了解他們是如何使用的坐昙。

1. 一次性調(diào)度

public static void main(String[] args) {
    Timer timer = new Timer();
    TimerTask task = new TimerTask() {
        @Override public void run() {
            SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss");
            System.out.println(format.format(scheduledExecutionTime()) + ", called");
        }
    };
    // 延遲一秒,打印一次
    // 打印結(jié)果如下:10:58:24, called
    timer.schedule(task, 1000);
}

2. 循環(huán)調(diào)度 - schedule()

public static void main(String[] args) {
    Timer timer = new Timer();
    TimerTask task = new TimerTask() {
        @Override public void run() {
            SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss");
            System.out.println(format.format(scheduledExecutionTime()) + ", called");
        }
    };
    // 固定時(shí)間的調(diào)度方式芋忿,延遲一秒,之后每隔一秒打印一次
    // 打印結(jié)果如下:
    // 11:03:55, called
    // 11:03:56, called
    // 11:03:57, called
    // 11:03:58, called
    // 11:03:59, called
    // ...
    timer.schedule(task, 1000, 1000);
}

3. 循環(huán)調(diào)度 - scheduleAtFixedRate()

public static void main(String[] args) {
    Timer timer = new Timer();
    TimerTask task = new TimerTask() {
        @Override public void run() {
            SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss");
            System.out.println(format.format(scheduledExecutionTime()) + ", called");
        }
    };
    // 固定速率的調(diào)度方式疾棵,延遲一秒戈钢,之后每隔一秒打印一次
    // 打印結(jié)果如下:
    // 11:08:43, called
    // 11:08:44, called
    // 11:08:45, called
    // 11:08:46, called
    // 11:08:47, called
    // ...
    timer.scheduleAtFixedRate(task, 1000, 1000);
}

4. schedule()和scheduleAtFixedRate()的區(qū)別

從2和3的結(jié)果來看,他們達(dá)到的效果似乎是一樣的是尔。既然效果一樣殉了,JDK為啥要實(shí)現(xiàn)為兩個(gè)方法呢?他們應(yīng)該有不一樣的地方拟枚!

在正常的情況下薪铜,他們的效果是一模一樣的。而在異常的情況下 - 任務(wù)執(zhí)行的時(shí)間比間隔的時(shí)間更長(zhǎng)恩溅,他們是效果是不一樣的隔箍。

  1. schedule()方法,任務(wù)的下一次執(zhí)行時(shí)間是相對(duì)于上一次實(shí)際執(zhí)行完成的時(shí)間點(diǎn) 脚乡,因此執(zhí)行時(shí)間會(huì)不斷延后
  2. scheduleAtFixedRate()方法蜒滩,任務(wù)的下一次執(zhí)行時(shí)間是相對(duì)于上一次開始執(zhí)行的時(shí)間點(diǎn) ,因此執(zhí)行時(shí)間不會(huì)延后
  3. 由于Timer內(nèi)部是通過單線程方式實(shí)現(xiàn)的奶稠,所以這兩種方式都不存在線程安全的問題

我們先來看看schedule()的異常效果:

public static void main(String[] args) {
    Timer timer = new Timer();
    TimerTask task = new TimerTask() {
        @Override public void run() {
            SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(format.format(scheduledExecutionTime()) + ", called");
        }
    };

    timer.schedule(task, 1000, 2000);
    // 執(zhí)行結(jié)果如下:
    // 11:18:56, called
    // 11:18:59, called
    // 11:19:02, called
    // 11:19:05, called
    // 11:19:08, called
    // 11:19:11, called
}

接下來我們看看scheduleAtFixedRate()的異常效果:

public static void main(String[] args) {
    Timer timer = new Timer();
    TimerTask task = new TimerTask() {
        @Override public void run() {
            SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(format.format(scheduledExecutionTime()) + ", called");
        }
    };

    timer.scheduleAtFixedRate(task, 1000, 2000);
    // 執(zhí)行結(jié)果如下:
    // 11:20:45, called
    // 11:20:47, called
    // 11:20:49, called
    // 11:20:51, called
    // 11:20:53, called
    // 11:20:55, called
}

樓主一直相信俯艰,實(shí)踐是檢驗(yàn)真理比較好的方式,上面的例子從側(cè)面驗(yàn)證了我們最初的猜想锌订。

但是竹握,這兒引出了另外一個(gè)問題。既然Timer內(nèi)部是單線程實(shí)現(xiàn)的辆飘,在執(zhí)行間隔為2秒啦辐、任務(wù)實(shí)際執(zhí)行為3秒的情況下谓传,scheduleAtFixedRate是如何做到2秒輸出一次的呢?

【特別注意】
這兒其實(shí)是一個(gè)障眼法昧甘。需要重點(diǎn)關(guān)注的是良拼,打印方法輸出的值是通過調(diào)用scheduledExecutionTime()來生成的,而這個(gè)方法并不一定是任務(wù)真實(shí)執(zhí)行的時(shí)間充边,而是當(dāng)前任務(wù)應(yīng)該執(zhí)行的時(shí)間庸推。

源碼閱讀

樓主對(duì)于知識(shí)的理解是,除了知其然浇冰,還需要知其所以然贬媒。而閱讀源碼是打開知其所以然大門的一把強(qiáng)有力的鑰匙。在JDK中肘习,Timer主要由TimerTask际乘、TaskQueueTimerThread組成。

1. TimerTask

TimerTask表示任務(wù)調(diào)度器執(zhí)行的任務(wù)漂佩,繼承自Runnable脖含,其內(nèi)部維護(hù)著任務(wù)的狀態(tài),一共有4種狀態(tài)

  1. VIRGIN投蝉,英文名為處女养葵,表示任務(wù)還未調(diào)度
  2. SCHEDULED,已經(jīng)調(diào)度瘩缆,但還未執(zhí)行
  3. EXECUTED关拒,對(duì)于執(zhí)行一次的任務(wù),表示已經(jīng)執(zhí)行庸娱;對(duì)于重復(fù)執(zhí)行的任務(wù)着绊,該狀態(tài)無效
  4. CANCELLED,任務(wù)被取消

TimerTask還有下面的成員變量

  1. nextExecutionTime熟尉,下次執(zhí)行的時(shí)間
  2. period归露,任務(wù)執(zhí)行的時(shí)間間隔。正數(shù)表示固定速率臣樱;負(fù)數(shù)表示固定時(shí)延靶擦;0表示只執(zhí)行一次

分析完大致的功能之后,我們來看看其代碼雇毫。

/**
 * The state of this task, chosen from the constants below.
 */
int state = VIRGIN;

/**
 * This task has not yet been scheduled.
 */
static final int VIRGIN = 0;

/**
 * This task is scheduled for execution.  If it is a non-repeating task,
 * it has not yet been executed.
 */
static final int SCHEDULED   = 1;

/**
 * This non-repeating task has already executed (or is currently
 * executing) and has not been cancelled.
 */
static final int EXECUTED    = 2;

/**
 * This task has been cancelled (with a call to TimerTask.cancel).
 */
static final int CANCELLED   = 3;

TimerTask有兩個(gè)操作方法

  1. cancel() // 取消任務(wù)
  2. scheduledExecutionTime() // 獲取任務(wù)執(zhí)行時(shí)間

cancel()比較簡(jiǎn)單玄捕,主要對(duì)當(dāng)前任務(wù)加鎖,然后變更狀態(tài)為已取消棚放。

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

而在scheduledExecutionTime()中枚粘,任務(wù)執(zhí)行時(shí)間是通過下一次執(zhí)行時(shí)間減去間隔時(shí)間的方式計(jì)算出來的。

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

2. TaskQueue

TaskQueue是一個(gè)隊(duì)列飘蚯,在Timer中用于存放任務(wù)馍迄。其內(nèi)部是使用【最小堆算法】來實(shí)現(xiàn)的福也,堆頂?shù)娜蝿?wù)將最先被執(zhí)行。由于使用了【最小堆】攀圈,TaskQueue判斷執(zhí)行時(shí)間是否已到的效率極高暴凑。我們來看看其內(nèi)部是怎么實(shí)現(xiàn)的。

class TaskQueue {
    /**
     * Priority queue represented as a balanced binary heap: the two children
     * of queue[n] are queue[2*n] and queue[2*n+1].  The priority queue is
     * ordered on the nextExecutionTime field: The TimerTask with the lowest
     * nextExecutionTime is in queue[1] (assuming the queue is nonempty).  For
     * each node n in the heap, and each descendant of n, d,
     * n.nextExecutionTime <= d.nextExecutionTime.
     * 
     * 使用數(shù)組來存放任務(wù)
     */
    private TimerTask[] queue = new TimerTask[128];

    /**
     * The number of tasks in the priority queue.  (The tasks are stored in
     * queue[1] up to queue[size]).
     * 
     * 用于表示隊(duì)列中任務(wù)的個(gè)數(shù)赘来,需要注意的是现喳,任務(wù)數(shù)并不等于數(shù)組長(zhǎng)度
     */
    private int size = 0;

    /**
     * Returns the number of tasks currently on the queue.
     */
    int size() {
        return size;
    }

    /**
     * Adds a new task to the priority queue.
     * 
     * 往隊(duì)列添加一個(gè)任務(wù)
     */
    void add(TimerTask task) {
        // Grow backing store if necessary
        // 在任務(wù)數(shù)超過數(shù)組長(zhǎng)度,則通過數(shù)組拷貝的方式進(jìn)行動(dòng)態(tài)擴(kuò)容
        if (size + 1 == queue.length)
            queue = Arrays.copyOf(queue, 2*queue.length);

        // 將當(dāng)前任務(wù)項(xiàng)放入隊(duì)列
        queue[++size] = task;
        // 向上調(diào)整犬辰,重新形成一個(gè)最小堆
        fixUp(size);
    }

    /**
     * Return the "head task" of the priority queue.  (The head task is an
     * task with the lowest nextExecutionTime.)
     * 
     * 隊(duì)列的第一個(gè)元素就是最先執(zhí)行的任務(wù)
     */
    TimerTask getMin() {
        return queue[1];
    }

    /**
     * Return the ith task in the priority queue, where i ranges from 1 (the
     * head task, which is returned by getMin) to the number of tasks on the
     * queue, inclusive.
     * 
     * 獲取隊(duì)列指定下標(biāo)的元素
     */
    TimerTask get(int i) {
        return queue[i];
    }

    /**
     * Remove the head task from the priority queue.
     *
     * 移除堆頂元素嗦篱,移除之后需要向下調(diào)整,使之重新形成最小堆
     */
    void removeMin() {
        queue[1] = queue[size];
        queue[size--] = null;  // Drop extra reference to prevent memory leak
        fixDown(1);
    }

    /**
     * Removes the ith element from queue without regard for maintaining
     * the heap invariant.  Recall that queue is one-based, so
     * 1 <= i <= size.
     *
     * 快速移除指定位置元素幌缝,不會(huì)重新調(diào)整堆
     */
    void quickRemove(int i) {
        assert i <= size;

        queue[i] = queue[size];
        queue[size--] = null;  // Drop extra ref to prevent memory leak
    }

    /**
     * Sets the nextExecutionTime associated with the head task to the
     * specified value, and adjusts priority queue accordingly.
     *
     * 重新調(diào)度灸促,向下調(diào)整使之重新形成最小堆
     */
    void rescheduleMin(long newTime) {
        queue[1].nextExecutionTime = newTime;
        fixDown(1);
    }

    /**
     * Returns true if the priority queue contains no elements.
     *
     * 隊(duì)列是否為空
     */
    boolean isEmpty() {
        return size==0;
    }

    /**
     * Removes all elements from the priority queue.
     *
     * 清除隊(duì)列中的所有元素
     */
    void clear() {
        // Null out task references to prevent memory leak
        for (int i=1; i<=size; i++)
            queue[i] = null;

        size = 0;
    }

    /**
     * Establishes the heap invariant (described above) assuming the heap
     * satisfies the invariant except possibly for the leaf-node indexed by k
     * (which may have a nextExecutionTime less than its parent's).
     *
     * This method functions by "promoting" queue[k] up the hierarchy
     * (by swapping it with its parent) repeatedly until queue[k]'s
     * nextExecutionTime is greater than or equal to that of its parent.
     *
     * 向上調(diào)整,使之重新形成最小堆
     */
    private void fixUp(int k) {
        while (k > 1) {
            int j = k >> 1;
            if (queue[j].nextExecutionTime <= queue[k].nextExecutionTime)
                break;
            TimerTask tmp = queue[j];  queue[j] = queue[k]; queue[k] = tmp;
            k = j;
        }
    }

    /**
     * Establishes the heap invariant (described above) in the subtree
     * rooted at k, which is assumed to satisfy the heap invariant except
     * possibly for node k itself (which may have a nextExecutionTime greater
     * than its children's).
     *
     * This method functions by "demoting" queue[k] down the hierarchy
     * (by swapping it with its smaller child) repeatedly until queue[k]'s
     * nextExecutionTime is less than or equal to those of its children.
     *
     * 向下調(diào)整涵卵,使之重新形成最小堆
     */
    private void fixDown(int k) {
        int j;
        while ((j = k << 1) <= size && j > 0) {
            if (j < size &&
                queue[j].nextExecutionTime > queue[j+1].nextExecutionTime)
                j++; // j indexes smallest kid
            if (queue[k].nextExecutionTime <= queue[j].nextExecutionTime)
                break;
            TimerTask tmp = queue[j];  queue[j] = queue[k]; queue[k] = tmp;
            k = j;
        }
    }

    /**
     * Establishes the heap invariant (described above) in the entire tree,
     * assuming nothing about the order of the elements prior to the call.
     */
    void heapify() {
        for (int i = size/2; i >= 1; i--)
            fixDown(i);
    }
}

3. TimerThread

TimerThread作為Timer的成員變量浴栽,扮演著調(diào)度器的校色。我們先來看看它的構(gòu)造方法轿偎,作用主要就是持有任務(wù)隊(duì)列吃度。

TimerThread(TaskQueue queue) {
    this.queue = queue;
}

接下來看看run()方法,也就是線程執(zhí)行的入口贴硫。

public void run() {
    try {
        mainLoop();
    } finally {
        // Someone killed this Thread, behave as if Timer cancelled
        synchronized(queue) {
            newTasksMayBeScheduled = false;
            queue.clear();  // Eliminate obsolete references
        }
    }
}

主邏輯全在mainLoop()方法。在mainLoop方法執(zhí)行完之后伊者,會(huì)進(jìn)行資源的清理操作英遭。我們來看看mainLoop()方法。

private void mainLoop() {
    // while死循環(huán)
    while (true) {
        try {
            TimerTask task;
            boolean taskFired;
            // 對(duì)queue進(jìn)行加鎖亦渗,保證一個(gè)隊(duì)列里所有的任務(wù)都是串行執(zhí)行的
            synchronized(queue) {
                // Wait for queue to become non-empty
                // 操作1挖诸,隊(duì)列為空,需要等待新任務(wù)被調(diào)度法精,這時(shí)進(jìn)行wait操作
                while (queue.isEmpty() && newTasksMayBeScheduled)
                    queue.wait();
                // 這兒再次判斷隊(duì)列是否為空多律,是因?yàn)椤静僮?】有任務(wù)進(jìn)來了,同時(shí)任務(wù)又被取消了(進(jìn)行了`cancel`操作)搂蜓,
                // 這時(shí)如果隊(duì)列再次為空狼荞,那么需要退出線程,避免循環(huán)被卡死
                if (queue.isEmpty())
                    break; // Queue is empty and will forever remain; die

                // Queue nonempty; look at first evt and do the right thing
                long currentTime, executionTime;
                // 取出隊(duì)列中的堆頂元素(下次執(zhí)行時(shí)間最小的那個(gè)任務(wù))
                task = queue.getMin();
                // 這兒對(duì)堆元素進(jìn)行加鎖帮碰,是為了保證任務(wù)的可見性和原子性
                synchronized(task.lock) {
                    // 取消的任務(wù)將不再被執(zhí)行相味,需要從隊(duì)列中移除
                    if (task.state == TimerTask.CANCELLED) {
                        queue.removeMin();
                        continue;  // No action required, poll queue again
                    }
                    // 獲取系統(tǒng)當(dāng)前時(shí)間和任務(wù)下次執(zhí)行的時(shí)間
                    currentTime = System.currentTimeMillis();
                    executionTime = task.nextExecutionTime;

                    // 任務(wù)下次執(zhí)行的時(shí)間 <= 系統(tǒng)當(dāng)前時(shí)間,則執(zhí)行此任務(wù)(設(shè)置狀態(tài)標(biāo)記`taskFired`為true)
                    if (taskFired = (executionTime<=currentTime)) {
                        // `peroid`為0殉挽,表示此任務(wù)只需執(zhí)行一次
                        if (task.period == 0) { // Non-repeating, remove
                            queue.removeMin();
                            task.state = TimerTask.EXECUTED;
                        }
                        // period不為0丰涉,表示此任務(wù)需要重復(fù)執(zhí)行
                        // 在這兒就體現(xiàn)出了`schedule()`方法和`scheduleAtFixedRate()`的區(qū)別
                        else { // Repeating task, reschedule
                            queue.rescheduleMin(
                              task.period<0 ? currentTime   - task.period
                                            : executionTime + task.period);
                        }
                    }
                }
                // 任務(wù)沒有被觸發(fā)拓巧,隊(duì)列掛起(帶超時(shí)時(shí)間)
                if (!taskFired) // Task hasn't yet fired; wait
                    queue.wait(executionTime - currentTime);
            }
            // 任務(wù)被觸發(fā),執(zhí)行任務(wù)一死。執(zhí)行完后進(jìn)入下一輪循環(huán)
            if (taskFired)  // Task fired; run it, holding no locks
                task.run();
        } catch(InterruptedException e) {
        }
    }
}

4. Timer

Timer通過構(gòu)造方法做了下面的事情:

  1. 填充TimerThread對(duì)象的各項(xiàng)屬性(比如線程名字肛度、是否守護(hù)線程)
  2. 啟動(dòng)線程
/**
 * The timer thread.
 */
private final TimerThread thread = new TimerThread(queue);

public Timer(String name, boolean isDaemon) {
    thread.setName(name);
    thread.setDaemon(isDaemon);
    thread.start();
}

Timer中,真正的暴露給用戶使用的調(diào)度方法只有兩個(gè)投慈,schedule()scheduleAtFixedRate()承耿,我們來看看。

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

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

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 firstTime, long period) {
    if (period <= 0)
        throw new IllegalArgumentException("Non-positive period.");
    sched(task, firstTime.getTime(), -period);
}

public void scheduleAtFixedRate(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 scheduleAtFixedRate(TimerTask task, Date firstTime,
                                long period) {
    if (period <= 0)
        throw new IllegalArgumentException("Non-positive period.");
    sched(task, firstTime.getTime(), period);
}

從上面的代碼我們看出下面幾點(diǎn)逛裤。

  1. 這兩個(gè)方法最終都調(diào)用了sched()私有方法
  2. schedule()傳入的period為負(fù)數(shù)瘩绒,scheduleAtFixedRate()傳入的period為正數(shù)

接下來我們看看sched()方法。

private void sched(TimerTask task, long time, long period) {
    // 1. `time`不能為負(fù)數(shù)的校驗(yàn)
    if (time < 0)
        throw new IllegalArgumentException("Illegal execution time.");

    // Constrain value of period sufficiently to prevent numeric
    // overflow while still being effectively infinitely large.
    // 2. `period`不能超過`Long.MAX_VALUE >> 1`
    if (Math.abs(period) > (Long.MAX_VALUE >> 1))
        period >>= 1;

    synchronized(queue) {
        // 3. Timer被取消時(shí)带族,不能被調(diào)度
        if (!thread.newTasksMayBeScheduled)
            throw new IllegalStateException("Timer already cancelled.");

        // 4. 對(duì)任務(wù)加鎖锁荔,然后設(shè)置任務(wù)的下次執(zhí)行時(shí)間、執(zhí)行周期和任務(wù)狀態(tài)蝙砌,保證任務(wù)調(diào)度和任務(wù)取消是線程安全的
        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;
        }
        // 5. 將任務(wù)添加進(jìn)隊(duì)列
        queue.add(task);
        // 6. 隊(duì)列中如果堆頂元素是當(dāng)前任務(wù)阳堕,則喚醒隊(duì)列,讓`TimerThread`可以進(jìn)行任務(wù)調(diào)度
        if (queue.getMin() == task)
            queue.notify();
    }
}

sched()方法經(jīng)過了下述步驟:

  1. time不能為負(fù)數(shù)的校驗(yàn)
  2. period不能超過Long.MAX_VALUE >> 1
  3. Timer被取消時(shí)择克,不能被調(diào)度
  4. 對(duì)任務(wù)加鎖恬总,然后設(shè)置任務(wù)的下次執(zhí)行時(shí)間、執(zhí)行周期和任務(wù)狀態(tài)肚邢,保證任務(wù)調(diào)度和任務(wù)取消是線程安全的
  5. 將任務(wù)添加進(jìn)隊(duì)列
  6. 隊(duì)列中如果堆頂元素是當(dāng)前任務(wù)壹堰,則喚醒隊(duì)列,讓TimerThread可以進(jìn)行任務(wù)調(diào)度

【說明】:我們需要特別關(guān)注一下第6點(diǎn)骡湖。為什么堆頂元素必須是當(dāng)前任務(wù)時(shí)才喚醒隊(duì)列呢贱纠?原因在于堆頂元素所代表的意義,即:堆頂元素表示離當(dāng)前時(shí)間最近的待執(zhí)行任務(wù)响蕴!
【例子1】:假如當(dāng)前時(shí)間為1秒谆焊,隊(duì)列里有一個(gè)任務(wù)A需要在3秒執(zhí)行,我們新加入的任務(wù)B需要在5秒執(zhí)行浦夷。這時(shí)辖试,因?yàn)?code>TimerThread有wait(timeout)操作,時(shí)間到了會(huì)自己?jiǎn)拘雅K詾榱诵阅芸紤]罐孝,不需要在sched()操作的時(shí)候進(jìn)行喚醒。
【例子2】:假如當(dāng)前時(shí)間為1秒肥缔,隊(duì)列里有一個(gè)任務(wù)A需要在3秒執(zhí)行肾档,我們新加入的任務(wù)B需要在2秒執(zhí)行。這時(shí),如果不在sched()中進(jìn)行喚醒操作怒见,那么任務(wù)A將在3秒時(shí)執(zhí)行俗慈。而任務(wù)B因?yàn)樾枰?秒執(zhí)行,已經(jīng)過了它應(yīng)該執(zhí)行的時(shí)間遣耍,從而出現(xiàn)問題闺阱。

任務(wù)調(diào)度方法sched()分析完之后,我們繼續(xù)分析其他方法舵变。先來看一下cancel()酣溃,該方法用于取消Timer的執(zhí)行。

 public void cancel() {
    synchronized(queue) {
        thread.newTasksMayBeScheduled = false;
        queue.clear();
        queue.notify();  // In case queue was already empty.
    }
}

從上面源碼分析來看纪隙,該方法做了下面幾件事情:

  1. 設(shè)置TimerThreadnewTasksMayBeScheduled標(biāo)記為false
  2. 清空隊(duì)列
  3. 喚醒隊(duì)列

有的時(shí)候赊豌,在一個(gè)Timer中可能會(huì)存在多個(gè)TimerTask。如果我們只是取消其中幾個(gè)TimerTask绵咱,而不是全部碘饼,除了對(duì)TimerTask執(zhí)行cancel()方法調(diào)用,還需要對(duì)Timer進(jìn)行清理操作悲伶。這兒的清理方法就是purge()艾恼,我們來看看其實(shí)現(xiàn)邏輯。

public int purge() {
     int result = 0;

     synchronized(queue) {
         // 1. 遍歷所有任務(wù)麸锉,如果任務(wù)為取消狀態(tài)钠绍,則將其從隊(duì)列中移除,移除數(shù)做加一操作
         for (int i = queue.size(); i > 0; i--) {
             if (queue.get(i).state == TimerTask.CANCELLED) {
                 queue.quickRemove(i);
                 result++;
             }
         }
         // 2. 將隊(duì)列重新形成最小堆
         if (result != 0)
             queue.heapify();
     }

     return result;
 }

5. 喚醒隊(duì)列的方法

通過前面源碼的分析花沉,我們看到隊(duì)列的喚醒存在于下面幾處:

  1. Timer.cancel()
  2. Timer.sched()
  3. Timer.threadReaper.finalize()

第一點(diǎn)和第二點(diǎn)其實(shí)已經(jīng)分析過了柳爽,下面我們來看看第三點(diǎn)。

private final Object threadReaper = new Object() {
    protected void finalize() throws Throwable {
        synchronized(queue) {
            thread.newTasksMayBeScheduled = false;
            queue.notify(); // In case queue is empty.
        }
    }
};

該方法用于在GC階段對(duì)任務(wù)隊(duì)列進(jìn)行喚醒碱屁,此處往往被讀者所遺忘泻拦。

那么,我們回過頭來想一下忽媒,為什么需要這段代碼呢?

我們?cè)诜治?code>TimerThread的時(shí)候看到:如果Timer創(chuàng)建之后腋粥,沒有被調(diào)度的話晦雨,將一直wait,從而陷入假死狀態(tài)隘冲。為了避免這種情況闹瞧,并發(fā)大師Doug Lea機(jī)智地想到了在finalize()中設(shè)置狀態(tài)標(biāo)記newTasksMayBeScheduled,并對(duì)任務(wù)隊(duì)列進(jìn)行喚醒操作(queue.notify())展辞,將TimerThread從死循環(huán)中解救出來奥邮。

總結(jié)

首先,本文演示了Timer是如何使用的,然后分析了調(diào)度方法schedule()scheduleAtFixedRate()的區(qū)別和聯(lián)系洽腺。

然后脚粟,為了加深我們對(duì)Timer的理解,我們通過閱讀源碼的方式進(jìn)行了深入的分析蘸朋『宋蓿可以看得出,其內(nèi)部實(shí)現(xiàn)得非常巧妙藕坯,考慮得也很完善团南。

但是因?yàn)?code>Timer串行執(zhí)行的特性,限制了其在高并發(fā)下的運(yùn)用炼彪。后面我們將深入分析高并發(fā)吐根、分布式環(huán)境下的任務(wù)調(diào)度是如何實(shí)現(xiàn)的,讓我們拭目以待吧~

參考鏈接

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末辐马,一起剝皮案震驚了整個(gè)濱河市拷橘,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌齐疙,老刑警劉巖膜楷,帶你破解...
    沈念sama閱讀 211,265評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異贞奋,居然都是意外死亡赌厅,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門轿塔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來特愿,“玉大人,你說我怎么就攤上這事勾缭∽嵴希” “怎么了?”我有些...
    開封第一講書人閱讀 156,852評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵俩由,是天一觀的道長(zhǎng)毒嫡。 經(jīng)常有香客問我,道長(zhǎng)幻梯,這世上最難降的妖魔是什么兜畸? 我笑而不...
    開封第一講書人閱讀 56,408評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮碘梢,結(jié)果婚禮上咬摇,老公的妹妹穿的比我還像新娘。我一直安慰自己煞躬,他們只是感情好肛鹏,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評(píng)論 5 384
  • 文/花漫 我一把揭開白布逸邦。 她就那樣靜靜地躺著,像睡著了一般在扰。 火紅的嫁衣襯著肌膚如雪缕减。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,772評(píng)論 1 290
  • 那天健田,我揣著相機(jī)與錄音烛卧,去河邊找鬼。 笑死妓局,一個(gè)胖子當(dāng)著我的面吹牛总放,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播好爬,決...
    沈念sama閱讀 38,921評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼局雄,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了存炮?” 一聲冷哼從身側(cè)響起炬搭,我...
    開封第一講書人閱讀 37,688評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎穆桂,沒想到半個(gè)月后宫盔,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,130評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡享完,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評(píng)論 2 325
  • 正文 我和宋清朗相戀三年灼芭,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片般又。...
    茶點(diǎn)故事閱讀 38,617評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡彼绷,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出茴迁,到底是詐尸還是另有隱情寄悯,我是刑警寧澤,帶...
    沈念sama閱讀 34,276評(píng)論 4 329
  • 正文 年R本政府宣布堕义,位于F島的核電站猜旬,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏倦卖。R本人自食惡果不足惜洒擦,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望糖耸。 院中可真熱鬧,春花似錦丘薛、人聲如沸嘉竟。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽舍扰。三九已至倦蚪,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間边苹,已是汗流浹背陵且。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評(píng)論 1 265
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留个束,地道東北人慕购。 一個(gè)月前我還...
    沈念sama閱讀 46,315評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像茬底,于是被迫代替她去往敵國(guó)和親沪悲。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • Timer 定時(shí)器相信都不會(huì)陌生阱表,之所以拿它來做源碼分析殿如,是發(fā)現(xiàn)整個(gè)控制流程可以體現(xiàn)很多有意思的東西。 在業(yè)務(wù)開發(fā)...
    石先閱讀 6,334評(píng)論 2 13
  • Timer和TimerTask Timer就是一個(gè)調(diào)度器最爬,而TimerTask只是一個(gè)實(shí)現(xiàn)了run方法的類涉馁,而具體...
    史路比閱讀 373評(píng)論 0 2
  • 完全公平調(diào)度CFS CFS(Completely Fair Scheduler)試圖按照對(duì) CPU 時(shí)間的 “最大...
    batbattle閱讀 3,343評(píng)論 0 5
  • java.util.Timer:線程的工具,用于在后臺(tái)線程中安排將來執(zhí)行的任務(wù)爱致】舅停可以安排一次性執(zhí)行任務(wù),或定期重復(fù)...
    日光傾城_700b閱讀 1,607評(píng)論 0 3
  • 雪下的好大蒜鸡。今天寶返校拿成績(jī)胯努。中午接上寶,手里有喜報(bào)獎(jiǎng)品逢防,臉上喜滋滋的叶沛。我問她今天講試卷了嗎?她說講了忘朝。出現(xiàn)1+1...
    Acumey閱讀 157評(píng)論 0 0