JDK1.8源码解读之Timer

256 阅读12分钟

前言

  • 线程用于调度任务以在将来的后台线程中执行的工具。可以安排任务一次执行或定期执行重复任务。
  • 与每个Timer对象相对应的是一个单独的后台线程,该线程用于依次执行所有计时器的任务。
  • 计时器任务应快速完成。
  • 如果计时器任务花费过多时间来完成,它将“占用”计时器的任务执行线程。
  • 反过来,这可能会延迟后续任务的执行,这可能会“累加”并在(或是否)有问题的任务最终完成时快速连续执行。
  • 在最后一个对Timer对象的实时引用消失并且所有未完成的任务均已完成执行之后,计时器的任务执行线程会正常终止(并受到垃圾回收的影响)。
  • 但是,这可能要花很长时间。
  • 默认情况下,任务执行线程不会作为守护程序线程运行,因此它能够防止应用程序终止。
  • 如果调用方想快速终止计时器的任务执行线程,则调用方应调用计时器的cancel方法。
  • 如果计时器的任务执行线程意外终止,例如,由于调用了它的stop方法,
  • 则在计时器上计划任务的任何进一步尝试都将导致IllegalStateException,就好像计时器的cancel方法已被调用一样。
  • 此类是线程安全的:多个线程可以共享一个Timer对象,而无需外部同步。
  • 此类不提供实时保证:它使用Object.wait(long)方法调度任务。
  • Java 5.0引入了{@code java.util.concurrent}包,
  • 其中并发实用程序之一是 ScheduledThreadPoolExecutor,它是一个线程池,用于以给定的速率或延迟重复执行任务。
  • 实际上,它是{@code Timer} /{@code TimerTask}组合的更通用的替代品,
  • 因为它允许多个服务线程,接受各种时间单位,并且不需要子类化{@code TimerTask}(只需实现{@代码Runnable})。
  • 使用一个线程配置{@code ScheduledThreadPoolExecutor}使其等效于{@code Timer}。
  • 实施说明:此类可扩展为同时执行的大量计划任务(成千上万个问题不会出现)。
  • 在内部,它使用二进制堆表示其任务队列,因此调度任务的成本为O(log n),其中n是同时调度的任务数。
  • 实施注意事项:所有构造函数都启动一个计时器线程。

源码


package java.util;

public class Timer {
   
    private final TaskQueue queue = new TaskQueue();

    private final TimerThread thread = new TimerThread(queue);

    private final Object threadReaper = new Object() {
        protected void finalize() throws Throwable {
            synchronized(queue) {
                thread.newTasksMayBeScheduled = false;
                queue.notify(); // In case queue is empty.
            }
        }
    };
    
    private final static AtomicInteger nextSerialNumber = new AtomicInteger(0);
    private static int serialNumber() {
        return nextSerialNumber.getAndIncrement();
    }

    /**
     * 创建一个新的计时器。关联的线程不会作为守护程序运行}。
     */
    public Timer() {
        this("Timer-" + serialNumber());
    }

    /**
     * 创建一个新计时器,该计时器的关联线程可以指定为{@linkplain Thread#setDaemon作为守护程序运行}。
     * 如果计时器将用于安排重复的“维护活动”,则将调用守护程序线程,只要应用程序正在运行,就必须执行该线程,但不应延长应用程序的生命周期。
     */
    public Timer(boolean isDaemon) {
        this("Timer-" + serialNumber(), isDaemon);
    }

    /**
     * 创建一个新计时器,其关联线程具有指定的名称。关联的线程不会{@linkplain Thread#setDaemon作为守护程序运行}。
     */
    public Timer(String name) {
        thread.setName(name);
        thread.start();
    }

    /**
     * 创建一个新计时器,其关联线程具有指定的名称,并且可以指定为{@linkplain Thread#setDaemon作为守护程序运行}。
     */
    public Timer(String name, boolean isDaemon) {
        thread.setName(name);
        thread.setDaemon(isDaemon);
        thread.start();
    }

    /**
     * 安排指定的任务在指定的延迟后执行。
     */
    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);
    }

    /**
     * 从指定的延迟后开始,调度指定的任务以重复执行固定延迟。
     * 随后的执行大约间隔一定的间隔(以指定的时间间隔)进行。
     * 在固定延迟执行中,相对于上一次执行的实际执行时间安排每次执行。
     * 如果执行由于任何原因(例如垃圾回收或其他后台活动)而延迟,则后续执行也将延迟。
     * 从长远来看,执行频率通常会略低于指定周期的倒数(假设Object.wait(long)底层的系统时钟是准确的)。
     * 固定延迟执行适用于需要“平稳”的重复活动。
     * 换句话说,对于那些在短期内保持频率准确度比长期保持更为重要的活动是合适的。
     * 这包括大多数动画任务,例如定期闪烁光标。
     * 它还包括其中响应于人类输入来执行常规活动的任务,例如只要按住键就自动重复字符。
     */
    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);
    }

    /**
     * 从指定的时间开始,调度指定的任务以重复执行固定延迟。
     * 随后的执行大约每隔固定的时间间隔执行一次,并间隔指定的时间。
     * 在固定延迟执行中,相对于上一次执行的实际执行时间安排每次执行。
     * 如果执行由于任何原因(例如垃圾回收或其他后台活动)而延迟,则后续执行也将延迟。
     * 从长远来看,执行频率通常会略低于指定周期的倒数(假设Object.wait(long)底层的系统时钟是准确的)。
     * 由于上述原因,如果计划的第一次是过去的时间,则计划立即执行。
     * 固定延迟执行适用于需要“平稳”的重复活动。
     * 换句话说,对于那些在短期内保持频率准确度比长期保持更为重要的活动是合适的。
     * 这包括大多数动画任务,例如定期闪烁光标。
     * 它还包括其中响应于人类输入来执行常规活动的任务,例如只要按住键就自动重复字符。
     */
    public void schedule(TimerTask task, Date firstTime, long period) {
        if (period <= 0)
            throw new IllegalArgumentException("Non-positive period.");
        sched(task, firstTime.getTime(), -period);
    }

    /**
     * 从指定的延迟后开始,调度指定的任务以重复执行固定速率。
     * 随后的执行大约每隔固定的时间间隔执行一次,并间隔指定的时间。
     * 在固定速率执行中,相对于初始执行的计划执行时间来计划每个执行。
     * 如果执行由于某种原因(例如垃圾回收或其他后台活动)而延迟,则将快速连续发生两个或多个执行以“追上”。
     * 从长远来看,执行频率将恰好是指定时间段的倒数(假设Object.wait(long)底层的系统时钟是准确的)。
     * 固定速率执行适用于对绝对时间敏感的重复活动,例如每小时每小时发出一声提示音,或每天在特定时间运行计划的维护。
     * 对于执行固定次数执行的总时间很重要的重复活动,例如倒数计时器,它每秒钟滴答一次十秒钟,这也很适合。
     * 最后,固定速率执行适用于安排必须重复彼此同步的多个重复计时器任务。
     */
    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);
    }

    /**
     * 从指定的时间开始计划指定的任务以重复执行固定速率。
     * 随后的执行大约每隔固定的时间间隔执行一次,并间隔指定的时间。
     * 在固定速率执行中,相对于初始执行的计划执行时间来计划每个执行。
     * 如果执行由于某种原因(例如垃圾回收或其他后台活动)而延迟,则将快速连续发生两个或多个执行以“追上”。
     * 从长远来看,执行频率将恰好是指定时间段的倒数(假设Object.wait(long)底层的系统时钟是准确的)。
     * 由于上述原因,如果计划的第一次是过去的时间,那么将为任何“遗漏”的执行安排立即进行“追赶”执行。
     * 固定速率执行适用于对绝对时间敏感的重复活动,例如每小时每小时发出一声提示音,或每天在特定时间运行计划的维护。
     * 对于执行固定次数执行的总时间很重要的重复活动,例如倒数计时器,它每秒钟滴答一次十秒钟,这也很适合。
     * 最后,固定速率执行适用于安排必须重复彼此同步的多个重复计时器任务。
     */
    public void scheduleAtFixedRate(TimerTask task, Date firstTime,
                                    long period) {
        if (period <= 0)
            throw new IllegalArgumentException("Non-positive period.");
        sched(task, firstTime.getTime(), period);
    }

    /**
     * 计划指定的计时器任务以指定的时间在指定的时间(以毫秒为单位)执行。
     * 如果period为正,则安排任务重复执行;
     * 如果period为零,则将任务安排为一次性执行。
     * 时间以Date.getTime()格式指定。
     * 此方法检查计时器状态,任务状态和初始执行时间,但不检查周期。
     */
    private void sched(TimerTask task, long time, long period) {
        if (time < 0)
            throw new IllegalArgumentException("Illegal execution time.");

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

        synchronized(queue) {
            if (!thread.newTasksMayBeScheduled)
                throw new IllegalStateException("Timer already cancelled.");

            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;
            }

            queue.add(task);
            if (queue.getMin() == task)
                queue.notify();
        }
    }

    /**
     * 终止此计时器,放弃任何当前计划的任务。
     * 不干扰当前正在执行的任务(如果存在)。
     * 计时器终止后,其执行线程将正常终止,并且无法在其上安排更多任务。
     * 请注意,从此计时器调用的计时器任务的run方法中调用此方法绝对可以保证正在进行的任务执行是此计时器将执行的最后一个任务执行。
     */
    public void cancel() {
        synchronized(queue) {
            thread.newTasksMayBeScheduled = false;
            queue.clear();
            queue.notify();  // In case queue was already empty.
        }
    }

    /**
     * 从此计时器的任务队列中删除所有已取消的任务。
     * 调用此方法不会影响计时器的行为,但会从队列中删除对已取消任务的引用。
     * 如果没有外部引用这些任务,则它们就有资格进行垃圾回收。
     * 大多数程序将不需要调用此方法。它设计用于少数应用程序,该应用程序可取消大量任务。
     * 调用此方法会以时间换取空间:该方法的运行时间可能与n + c log n成正比,
     * 其中n是队列中的任务数,c是已取消任务的数。
     */
     public int purge() {
         int result = 0;

         synchronized(queue) {
             for (int i = queue.size(); i > 0; i--) {
                 if (queue.get(i).state == TimerTask.CANCELLED) {
                     queue.quickRemove(i);
                     result++;
                 }
             }

             if (result != 0)
                 queue.heapify();
         }

         return result;
     }
}

/**
 * 此“辅助程序类”实现了计时器的任务执行线程,该线程等待计时器队列上的任务,在它们触发时执行它们,重新计划重复的任务,
 * 并从队列中删除已取消的任务和已花费的非重复任务。
 */
class TimerThread extends Thread {
    /**
     * reaper将此标志设置为false,以通知我们不再有对Timer对象的实时引用。
     * 一旦此标志为true,并且队列中没有其他任务,就没有工作要做,因此我们可以正常终止。
     * 请注意,此字段受队列的监视器保护!
     */
    boolean newTasksMayBeScheduled = true;

    /**
     * 我们的计时器队列。我们优先于对Timer的引用存储此引用,因此该引用图保持非循环状态。
     * 否则,将永远不会对Timer进行垃圾回收,并且该线程也永远不会消失。
     */
    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
            }
        }
    }

    /**
     * The main timer loop.  (See class comment.)
     */
    private void mainLoop() {
        while (true) {
            try {
                TimerTask task;
                boolean taskFired;
                synchronized(queue) {
                    while (queue.isEmpty() && newTasksMayBeScheduled)
                        queue.wait();
                    if (queue.isEmpty())
                        break; 

                    // Queue nonempty; look at first evt and do the right thing
                    long currentTime, executionTime;
                    task = queue.getMin();
                    synchronized(task.lock) {
                        if (task.state == TimerTask.CANCELLED) {
                            queue.removeMin();
                            continue;
                        }
                        currentTime = System.currentTimeMillis();
                        executionTime = task.nextExecutionTime;
                        if (taskFired = (executionTime<=currentTime)) {
                            if (task.period == 0) {
                                queue.removeMin();
                                task.state = TimerTask.EXECUTED;
                            } else {
                                queue.rescheduleMin(
                                  task.period<0 ? currentTime   - task.period
                                                : executionTime + task.period);
                            }
                        }
                    }
                    if (!taskFired) 
                        queue.wait(executionTime - currentTime);
                }
                if (taskFired)
                    task.run();
            } catch(InterruptedException e) {
            }
        }
    }
}

/**
 * 此类表示计时器任务队列:TimerTasks的优先级队列,在nextExecutionTime上排序。
 * 每个Timer对象都有其中之一,并与TimerThread共享。
 * 在内部,此类使用堆,该堆为add,removeMin和rescheduleMin操作提供log(n)性能,并为getMin操作提供恒定时间性能。
 */
class TaskQueue {
    /**
     * 优先级队列表示为平衡的二叉树:queue [n]的两个子级是queue [2 *n]和queue [2 *n + 1]。
     * 优先级队列在nextExecutionTime字段上排序:
     * 具有最低nextExecutionTime的TimerTask在queue [1]中(假定队列为非空)。
     * 对于堆中的每个节点n,以及n,d的每个后代,n.nextExecutionTime <= d.nextExecutionTime。 
     */
    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]).
     */
    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.
     */
    void add(TimerTask task) {
        // Grow backing store if necessary
        if (size + 1 == queue.length)
            queue = Arrays.copyOf(queue, 2*queue.length);

        queue[++size] = task;
        fixUp(size);
    }

    /**
     * Return the "head task" of the priority queue.  (The head task is an
     * task with the lowest nextExecutionTime.)
     */
    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.
     */
    TimerTask get(int i) {
        return queue[i];
    }

    /**
     * Remove the head task from the priority queue.
     */
    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.
     */
    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.
     */
    void rescheduleMin(long newTime) {
        queue[1].nextExecutionTime = newTime;
        fixDown(1);
    }

    /**
     * Returns true if the priority queue contains no elements.
     */
    boolean isEmpty() {
        return size==0;
    }

    /**
     * Removes all elements from the priority queue.
     */
    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.
     */
    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;
        }
    }

    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);
    }
}

问题记录

  • Timer和线程之间的关系。