一、简介
Timer调度任务(TimerTask)以供将来在后台线程中执行的工具。任务可以安排为一次性执行,也可以安排为定期重复执行。
与每个<tt>Timer</tt>对应的对象是单个后台线程,后台线程用于按顺序执行Timer所有任务。Timer中的任务应快速完成。如果Timer中的任务需要过多的时间来完成,它将占用Timer对应的后台线程。这反过来又会延迟后续任务的执行,如果这种情况的任务最终发生时,后续的任务可能会"扎堆"并快速连续执行。
在对Timer对象的最后一个实时引用消失并且所有未完成的任务都已完成执行之后,Timer的任务执行线程TimerThread将正常终止(并成为垃圾收集的对象)。然而,这可能需要任意长的时间才能发生。默认情况下,任务执行线程TimerThread不作为守护线程运行,因此它能够防止应用程序终止。如果调用方希望快速终止Timer的任务执行线程TimerThread,则调用方应调用Timer的cancel方法。
如果Timer的任务执行线程TimerThread意外终止(例如,因为调用了Thread的stop方法,stop方法目前已废弃),则在Timer上调度任务的任何进一步尝试都将导致IllegalStateException,就好像调用了Timer的cancel方法一样。
这个类是线程安全的:多个线程可以共享一个Timer对象,而不需要外部同步,内部才用synchronized。
此类也不提供实时保证:使用Object.wait(long)方法调度任务。
Java5.0引入了{@code java.util.concurrent}包和其中的一个并发实用程序{@link java.util.concurrent.ScheduledThreadPoolExecutor ScheduledThreadPoolExecutor}它是一个重复执行的线程池以给定的速率和延迟执行任务。它实际上是{@code Timer}/{@code TimerTask}组合的通用替换,因为它允许多个服务线程,接受不同的 时间单位,并且不需要子类化{@code TimerTask}(只是实现{@code Runnable})。配置{@code 一个执行线程的ScheduledThreadPoolExecutor}等价于{@code Timer}。
实现说明:这个类扩展到大量并发调度的任务(数以千计的任务应该不会出现问题)。在内部,它使用二进制堆来表示其任务队列,因此调度任务的成本是O(log n),其中n是并发调度任务的数量。
实现说明:所有构造函数都启动一个TimerThread线程。
二、属性
/**
* Timer任务队列,此数据结构与TimerThread线程共享。Timer通过其各种调度调用在任务队列生成任务,
* TimerThread线程将消费、执行适当的计时器任务,并在它们过时时将其从队列中移除
*/
private final TaskQueue queue = new TaskQueue();
/**
* Timer中的线程
*/
private final TimerThread thread = new TimerThread(queue);
/**
* 此对象守护Timer的任务执行线程TimerThread优雅退出,当Timer对象没有引用指向,
* 并且Timer中的任务队列没有任务。它优先于Timer上的finalizer的执行,
* Timer上finalizer方法因为这样的终结器容易受到子类终结器忘记调用它的影响。
*/
private final Object threadReaper = new Object() {
protected void finalize() throws Throwable {
//使用任务队列做为锁对象,任务线程(TimerThread)不能执行,Timer不能往任务队列中加任务
synchronized(queue) {
//将Timer中TimerThread线程置为false,表示此线程不会获取任务队列中的任务进行执行,直接抛出IllegalStateException,在下面的sched方法进行介绍
thread.newTasksMayBeScheduled = false;
//通知TimerThread线程,防止TimerThread由于队列为空,等待
queue.notify();
}
}
};
/**
* 此ID用于生成线程名称。
*/
private final static AtomicInteger nextSerialNumber = new AtomicInteger(0);
三、内部类
- TimerThread
/** * 这个“helper类”实现Timer的任务执行线程,它 * 等待Timer队列上的任务,在任务它们启动(达到执行时间)时执行它们, * 重新安排重复的任务,并删除已取消的任务和消费 * 队列中的非重复任务。 */ class TimerThread extends Thread { /** * 上面的属性threadReaper将此标志设置为false,以通知我们不再有对Timer对象的引用。 * 一旦这个标志被设置为false,并且队列中没有更多的任务,就没有工作留给我们去做,所以我们优雅 * 地终止。请注意,此字段在修改时使用任务队列做为锁对象进行保护 */ boolean newTasksMayBeScheduled = true; /** * Timer队列对象。我们优先存储这个引用,而不是对Timer的引用,避免循环引用。 * 否则,Timer将永远不会被垃圾回收,线程永远不会消失,即TimerThread线程将永远不会被GC回收。 */ private TaskQueue queue; /** * TimerThread唯一构造函数 * * @param queue Timer中存放任务的队列 */ TimerThread(TaskQueue queue) { this.queue = queue; } /** * Thread的run方法,当线程被执行时,执行此方法 */ public void run() { try { //调用下面的mainLoop方法进行处理任务,mainLoop方法在下面详细进行介绍 mainLoop(); } finally { // 有人杀了这个线程,表现得好像调用Timer的cancel方法 // TimerThread在任务队列为空时,等待,在此线程等待期间或在等待之前如果有任何线程 // 中断了当前线程。将引发InterruptedException异常 synchronized(queue) { //将属性newTasksMayBeScheduled设置为false,表示此线程在任务队列中的任务执行 //完优雅退出,Timer不能往队列中添加任务,否则抛出IllegalStateException异常 newTasksMayBeScheduled = false; queue.clear(); // 清除队列中的任务,清除这些过时的任务引用 } } } /** * TimerThread中的主要处理逻辑,while(true)等待任务的到来 * 当TimerThread在任务队列为空时,等待,在此线程等待期间或在等待之前如果有任何线程 * 中断了当前线程。将引发InterruptedException异常,由于捕获InterruptedException异常, * 线程会退出不会抛异常 */ private void mainLoop() { while (true) { try { //Timer中的任务 TimerTask task; //任务是否达到触发时间 boolean taskFired; //使用任务队列做为锁对象,当线程获取到queue锁对象,Timer中的schedule、scheduleAtFixedRate //cancel、purge方法都不能马上执行,需要等待获取到queue锁对象 synchronized(queue) { // 等待队列变为非空,newTasksMayBeScheduled为true和队列为空,TimerThread线程才会进入等待状态,newTasksMayBeScheduled可以看上面的介绍 while (queue.isEmpty() && newTasksMayBeScheduled) queue.wait(); if (queue.isEmpty()) //当队列为空满足时,当调用Timer的cancel方法(thread.newTasksMayBeScheduled = false; //queue.clear();queue.notify();) break; // 任务队列为空,线程将退出 // 非空任务队列; 获取第一个任务进行处理(队列使用小项堆,获取最先执行的任务executionTime最小,堆在下面会进行介绍) //当前时间和第一个任务的执行时间 long currentTime, executionTime; //获取第一个任务,即executionTime最早的任务 task = queue.getMin(); //使用TimerTask中的lock属性做为锁对象,防止任务在执行,其他线程调用TimerTask的cancel、scheduledExecutionTime方法 synchronized(task.lock) { //如果任务已经取消CANCELLED,从任务队列中移除 if (task.state == TimerTask.CANCELLED) { //从队列中移除已取消的任务 queue.removeMin(); //继续获取下一个最先执行的任务 continue; // 不需要任何操作,重新轮询队列,获取最先执行的任务 } //获取当前时间 currentTime = System.currentTimeMillis(); //获取最先执行任务的执行时间 executionTime = task.nextExecutionTime; //判断最先执行任务的执行时间是否小于等于当前时间,如果小于等于,表明任务需要立即执行 if (taskFired = (executionTime<=currentTime)) { //task.period等于0,表明此任务只执行一次 if (task.period == 0) { // 不是重复执行的任务,移除 //将当前任务移除 queue.removeMin(); //将当前任务TimerTask的状态修改为EXECUTED,TimerTask的几种状态在下面会进行介绍 task.state = TimerTask.EXECUTED; } else { // 重复执行任务, 需重新进行调度,task.period<0表明任务固定延迟执行,即在当前时间的基础上加上延迟时间 // task.period>0表明任务固定速率执行,即在任务的执行时间基础上加上固定周期时间 queue.rescheduleMin( task.period<0 ? currentTime - task.period : executionTime + task.period); } } } if (!taskFired) // 当前任务尚未达到执行时间;等待 //调用队列的wait方法传入执行时间减去当前时间的超时时间,如果有新的任务传入,执行时间更新,当前线程也会被唤醒 queue.wait(executionTime - currentTime); } //如果当前任务已经达到执行时间 if (taskFired) // 任务已达到执行时间; 在无锁条件下运行任务 //运行任务 task.run(); } catch(InterruptedException e) { //忽略InterruptedException异常,线程优雅终止 } } } } - TaskQueue
/** * 此类表示Timer任务队列: TimerTasks的优先级队列,使用TimerTask的nextExecutionTime进行排序 * 每个Timer对象都有一个,它与TimerThread共享其中一个TaskQueue任务队列。 * 在内部,这个类使用堆,此类中的add、removeMin和rescheduleMin操作提供log(n)性能, * 为getMin操作提供常量时间性能。 * 使用小项堆 */ class TaskQueue { /** * 表示为平衡二进制堆的优先级队列:queue[n]有两个子队列queue[2*n]和queue[2*n+1]。 * 优先级队列是TimerTask的nextExecutionTime字段上进行排序:具有最低nextExecutionTime的TimerTask * 位于queue[1](假设队列为非空)。对于堆中的每个节点n,n.nextExecutionTime<=d.nextExecutionTime的每个后代d * 即每个任务节点的nextExecutionTime小于子任务节点的nextExecutionTime * 堆使用数组进行存储可以节省空间 */ private TimerTask[] queue = new TimerTask[128]; /** * 优先级队列中任务数。(任务存储在queue[1]到queue[size]),任务存储从1开始可以更好的计算出子节点,节点n的两个子节点,queue[2*n],queue[2*n+1] */ private int size = 0; /** * 返回队列中当前任务的数目 */ int size() { return size; } /** * 将新任务添加到优先级队列。 */ void add(TimerTask task) { // 必要时增加队列数组的长度,判断当前队列中任务数目是否等于队列数组长度,相等表明队列数组长度已满,需要扩容 if (size + 1 == queue.length) //将任务队列数组扩大2倍 queue = Arrays.copyOf(queue, 2*queue.length); //将新建的任务放在队列的末尾 queue[++size] = task; //使用上堆,将当前任务的nextExecutionTime和父的nextExecutionTime进行比较,父的nextExecutionTime如果比较大,交换两个位置,以此反复,直到不满足或到头结点,下面进行介绍 fixUp(size); } /** * 返回优先级队列的“head task”。(”head task“是 * nextExecutionTime最低的任务。) */ TimerTask getMin() { //队列数组的第一个元素,从下标1开始存储元素 return queue[1]; } /** * 返回优先级队列中的第i个任务,其中i的范围为1( * head task,由getMin返回)到队列中包含的任务数size */ TimerTask get(int i) { //队列数组的第i个元素,从下标1开始存储元素 return queue[i]; } /** * 从优先级队列中删除头任务(nextExecutionTime最低的任务,即最先执行的任务)。 */ void removeMin() { //将最后一个任务移动到任务队列下标为1的位置 queue[1] = queue[size]; //将最后一个下标位置元素置为空 queue[size--] = null; // 删除额外引用以防止内存泄漏 //使用下堆,将当前任务的nextExecutionTime和两个子任务队列的nextExecutionTime进行比较,子任务队列中nextExecutionTime如果比较小, //和两个子任务队列中最小nextExecutionTime交换位置,以此反复,直到不满足或到尾结点,下面进行介绍 fixDown(1); } /** * 从队列中删除第i个元素,而不重新维护堆,即不重新进行堆化,目前在Timer中的purge()方法中调用 * purge()中将队列中状态为CANCELLED的TimerTask移除,即将任务队列中已取消的任务移除,移除完,再最外层统一进行堆化 * 任务队列是从下标1进行存储,为此队列的目前存放任务的范围为1 <= i <= size */ void quickRemove(int i) { //断言i <= size assert i <= size; //将最后一个任务移动到任务队列下标为i的位置 queue[i] = queue[size]; //将最后一个下标位置元素置为空 queue[size--] = null; // 删除额外引用以防止内存泄漏 } /** * 将与head任务关联的nextExecutionTime设置为指定的值,并相应地调整优先级队列。 * 在TimerThread中mainLoop方法进行调用,对重复的任务进行重新计算执行时间,重新加到队列中 */ void rescheduleMin(long newTime) { //将与head任务关联的nextExecutionTime设置为指定的值 queue[1].nextExecutionTime = newTime; //使用下堆,将当前任务的nextExecutionTime和两个子任务队列的nextExecutionTime进行比较,子任务队列中nextExecutionTime如果比较小, //和两个子任务队列中最小nextExecutionTime交换位置,以此反复,直到不满足或到尾结点,下面进行介绍 fixDown(1); } /** * 如果优先级队列不包含任何任务元素,则返回true. */ boolean isEmpty() { return size==0; } /** * 从优先级队列中删除所有元素。 * 这里不需要进行加锁,外层调用方已经进行加锁 */ void clear() { // 清空任务引用以防止内存泄漏 for (int i=1; i<=size; i++) queue[i] = null; //将优先级队列中任务数置为0 size = 0; } /** * 当新的任务重新加到队列,即加到队列的末尾,需重新维护堆的特性(①堆必须是一个完全二叉树, * 完全二叉树要求,除了最后一层,其他层的节点个数都是满的,最后一层的节点都靠左排列②堆中每一个节点的值都必须 * 大于等于(或小于等于)其子树中每个节点的值,这里是小于其子树中每个节点的值) * 重新加入的k索引节点(其nextExecutionTime可能小于其父级) * * 此方法通过在层次结构中“提升”队列[k]来工作 *(通过与其父级交换)重复,直到队列[k]的 * nextExecutionTime大于或等于其父级 */ private void fixUp(int k) { //循环直到根节点,任务队列即从下标1进行存储任务 while (k > 1) { //获取节点k的父节点j int j = k >> 1; //如果当前节点k执行时间nextExecutionTime大于父j的执行时间nextExecutionTime,直接退出 if (queue[j].nextExecutionTime <= queue[k].nextExecutionTime) //直接退出 break; //交换j和k两个元素 TimerTask tmp = queue[j]; queue[j] = queue[k]; queue[k] = tmp; //将父节点j赋值给k,一直往上进行堆化,直到队列[k]的nextExecutionTime大于或等于其父级 k = j; } } /** * 当任务从队列中移除,或重复任务的执行时间修改,或重新进行堆化,需重新维护堆的特性(①堆必须是一个完全二叉树, * 完全二叉树要求,除了最后一层,其他层的节点个数都是满的,最后一层的节点都靠左排列②堆中每一个节点的值都必须 * 大于等于(或小于等于)其子树中每个节点的值,这里是小于其子树中每个节点的值) * 删除任务节点时将队列末尾中的k索引节点放到头节点(k任务节点其nextExecutionTime可能大于其两个子队列节点) * * 此方法通过在层次结构中“提升”队列[k]来工作 *(通过与较小的子项进行交换)重复,直到队列[k]的 * nextExecutionTime小于或等于其子级 */ private void fixDown(int k) { int j; //获取k节点的左子节点,如果左子节点j小于队列存在的任务数目(任务队列从下标1开始进行存储) //表明k节点至少存在一个左子节点,j > 0防止k<<1溢出 while ((j = k << 1) <= size && j > 0) { //如果j小于size,表明k存在右子节点j+1,比较左右两个子节点中nextExecutionTime,获取nextExecutionTime最小的那个子任务节点下标 if (j < size && queue[j].nextExecutionTime > queue[j+1].nextExecutionTime) j++; // j指向nextExecutionTime最小的子节点 //判断父节点k的nextExecutionTime是否比最小的子节点j的nextExecutionTime更小,如果k的nextExecutionTime更小直接退出 if (queue[k].nextExecutionTime <= queue[j].nextExecutionTime) break; //交换j和k两个元素 TimerTask tmp = queue[j]; queue[j] = queue[k]; queue[k] = tmp; //将子节点j赋值给k,一直往下进行堆化,直到队列[k]的nextExecutionTime小于或等于其子级 k = j; } } /** * 重新维护堆的特性,在调用之前不考虑元素的顺序,由此方法统一进行堆化,目前在Timer中的purge()方法中调用 * purge将取消的任务将任务队列中移除,然后调用heapify进行统一堆化,如果堆存储数组从1开始存储, * size/2是最后一个有叶子节点的节点,从size/2+1 到 size的都是叶子节点 * 从size/2位置到1进行往下进行堆化 */ void heapify() { 从size/2位置到1进行往下进行堆化 for (int i = size/2; i >= 1; i--) //下堆下标为i的节点 fixDown(i); } }
四、构造函数
/**
* 创建新的Timer对象。关联的线程TimerThread不作为守护线程运行
* {@linkplain Thread#setDaemon}.
*/
public Timer() {
this("Timer-" + serialNumber());
}
/**
* 创建一个新的Timer对象。关联的线程TimerThread可以指定为
* {@linkplain Thread#setDaemon 作为守护线程运行}.
* 守护线程被调用,如果Timer将用于调度重复的"维护活动",只要应用程序正在运行,就必须执行这些活动
* 但不应延长应用程序的生命周期
*
* @param isDaemon 如果关联的线程应作为守护线程运行,则isDaemon为true
*/
public Timer(boolean isDaemon) {
this("Timer-" + serialNumber(), isDaemon);
}
/**
* 创建关联线程具有指定名称的Timer对象.
* 关联的线程TimerThread不作为守护线程运行
* {@linkplain Thread#setDaemon run as a daemon}.
*
* @param name 关联的线程TimerThread的名称
* @throws NullPointerException 如果 {@code name} is null
* @since 1.5
*/
public Timer(String name) {
//设置TimerThread线程名称
thread.setName(name);
//启动线程,即运行TimerThread.run()方法,TimerThread可以看上面内部类的介绍
thread.start();
}
/**
* Creates a new timer whose associated thread has the specified name,
* and may be specified to
* {@linkplain Thread#setDaemon run as a daemon}.
*
* @param name 关联的线程TimerThread的名称
* @param isDaemon 如果关联的线程应作为守护线程运行,则isDaemon为true
* @throws NullPointerException 如果 {@code name} is null
* @since 1.5
*/
public Timer(String name, boolean isDaemon) {
//设置TimerThread线程名称
thread.setName(name);
//设置TimerThread是否为守护线程
thread.setDaemon(isDaemon);
//启动线程,即运行TimerThread.run()方法,TimerThread可以看上面内部类的介绍
thread.start();
}五、调度、取消、清除任务方法
/**
* 安排指定任务在指定延迟后执行,任务只会被执行一次,不是重复任务。
*
* @param task 要安排的任务.
* @param delay 任务执行前的延迟(毫秒)。
* @throws IllegalArgumentException 如果 <tt>delay</tt> 为负数, 或者
* <tt>delay + System.currentTimeMillis()</tt> 为负数,即long数值相加溢出.
* @throws IllegalStateException 如果任务已经被安排或者任务已经取消
* , 或Timer已经取消, 或者 TimerThread已终止.
* @throws NullPointerException 如果 {@code task} is null
*/
public void schedule(TimerTask task, long delay) {
if (delay < 0)
//如果 <tt>delay</tt> 为负数,抛出IllegalArgumentException异常
throw new IllegalArgumentException("Negative delay.");
//调用sched方法,传入要安排的任务,第一次执行任务的时间,和下一次执行的周期时间,sched方法下面进行介绍
sched(task, System.currentTimeMillis()+delay, 0);
}
/**
* 安排指定任务再指定时间执行. 如果时间过去了,任务被安排立即执行,
* 任务只会被执行一次,不是重复任务。
*
* @param task 要安排的任务.
* @param time 执行任务的时间.
* @throws IllegalArgumentException 如果 <tt>time.getTime()</tt> 负数.
* @throws IllegalStateException 如果任务已经被安排或者任务已经取消
* , 或Timer已经取消, 或者 TimerThread已终止.
* @throws NullPointerException 如果 {@code task} 或者 {@code time} is null
*/
public void schedule(TimerTask task, Date time) {
//调用sched方法,传入要安排的任务,第一次执行任务的时间,和下一次执行的周期时间,sched方法下面进行介绍
sched(task, time.getTime(), 0);
}
/**
* 从指定的延迟后开始执行,为重复的固定延迟执行计划的指定任务。随后的发生以大约固定的间隔进行,
* 间隔由指定的时间段隔开,即当前任务执行时间完的当前时间currentTime = System.currentTimeMillis()
* 则当前任务的下一次执行时间为currentTime+delay
*
* 在固定延迟执行中,当前任务的每个执行都是相对于上一次执行完的实际执行时间。如果当前任务的执行延迟
* 由于任何原因(例如垃圾收集或其他后台活动),随后的当前任务的下一次执行也将被推迟.
* 从长远来看,执行的频率通常会稍微低于指定周期的倒数(假设系统底层时钟Object.wait(long)是准确的。
* 物质在1s内完成周期性变化的次数叫做频率。
*
* 固定延迟执行适用于经常性活动也就是说,它适合于保持频率准确更重要的活动
* 从短期来看比从长期来看。这包括大多数动画任务,如定期闪烁光标。
* 它还包括响应人工输入执行常规活动的任务,例如自动重复一个字符,只要按照一个键
*
* @param task 要安排的任务.
* @param delay 任务执行前的延迟(毫秒).
* @param period 重复任务重新执行之间的时间间隔,间隔时间(毫秒).
* @throws IllegalArgumentException 如果 {@code delay < 0}, 或者
* {@code delay + System.currentTimeMillis() < 0}为负数,即long数值相加溢出, 或者
* {@code period <= 0}
* @throws IllegalStateException 如果任务已经被安排或者任务已经取消
* , 或Timer已经取消, 或者 TimerThread已终止.
* @throws NullPointerException 如果 {@code task} is null
*/
public void schedule(TimerTask task, long delay, long period) {
if (delay < 0)
//如果delay为负数,抛出IllegalArgumentException异常
throw new IllegalArgumentException("Negative delay.");
if (period <= 0)
//如果period为负数,抛出IllegalArgumentException异常
throw new IllegalArgumentException("Non-positive period.");
//调用sched方法,传入要安排的任务,第一次执行任务的时间,和下一次执行的周期时间,sched方法下面进行介绍
sched(task, System.currentTimeMillis()+delay, -period);
}
/**
* 从指定的时间开始执行,为重复的固定延迟执行计划指定的任务,随后的发生以大约固定的间隔进行
* 间隔由指定的时间段隔开,即当前任务执行时间完的当前时间currentTime = System.currentTimeMillis()
* 则当前任务的下一次执行时间为currentTime+delay
*
* 在固定延迟执行中,当前任务的每个执行都是相对于上一次执行完的实际执行时间。如果当前任务的执行延迟
* 由于任何原因(例如垃圾收集或其他后台活动),随后的当前任务的下一次执行也将被推迟.
* 从长远来看,执行的频率通常会稍微低于指定周期的倒数(假设系统底层时钟Object.wait(long)是准确的。
* 物质在1s内完成周期性变化的次数叫做频率。
*
* 固定延迟执行适用于经常性活动也就是说,它适合于保持频率准确更重要的活动
* 从短期来看比从长期来看。这包括大多数动画任务,如定期闪烁光标。
* 它还包括响应人工输入执行常规活动的任务,例如自动重复一个字符,只要按照一个键
*
* @param task 要安排的任务.
* @param firstTime 第一次执行任务的时间.
* @param period 重复任务重新执行之间的时间间隔,间隔时间(毫秒).
* @throws IllegalArgumentException 如果 {@code firstTime.getTime() < 0}, 或者
* {@code period <= 0}
* @throws IllegalStateException 如果任务已经被安排或者任务已经取消
* , 或Timer已经取消, 或者 TimerThread已终止.
* @throws NullPointerException 如果 {@code task} 或者 {@code firstTime} is null
*/
public void schedule(TimerTask task, Date firstTime, long period) {
if (period <= 0)
//如果period为负数,抛出IllegalArgumentException异常
throw new IllegalArgumentException("Non-positive period.");
//调用sched方法,传入要安排的任务,第一次执行任务的时间,和下一次执行的周期时间,sched方法下面进行介绍
sched(task, firstTime.getTime(), -period);
}
/**
* 从指定的延迟后开始执行,为重复的固定速率执行计划指定的任务。随后的发生以大约固定的间隔进行
* 间隔由指定的时间段隔开,即当前任务执行时间executionTime = task.nextExecutionTime;
* 则当前任务的下一次执行时间为executionTime + delay
*
* 在固定速率执行中,每个执行都是相对于初始执行的计划执行时间。如果执行是
* 由于任何原因(如垃圾收集或其他后台活动)而延迟,两个或多个执行将快速连续发生以“赶上”。
* 从长远来看,执行频率将正好是指定周期的倒数(假设系统时钟在底层Object.wait(Long)是准确的.
*
* 固定速率执行适用于经常性活动对绝对时间敏感,例如每一小时一小时,或每天在特定时间。它也适用于经常性活动
* 其中执行固定数量的执行的总时间是重要的,例如每秒计时一次的倒计时十秒钟。最后,固定速率执行适合于
* 调度多个必须保持彼此同步的重复计时器任务。
*
* @param task 要安排的任务.
* @param delay 任务执行前的延迟(毫秒).
* @param period 重复任务重新执行之间的时间间隔,间隔时间(毫秒).
* @throws IllegalArgumentException 如果 {@code delay < 0}, 或者
* {@code delay + System.currentTimeMillis() < 0}为负数,即long数值相加溢出, 或者
* {@code period <= 0}
* @throws IllegalStateException 如果任务已经被安排或者任务已经取消
* , 或Timer已经取消, 或者 TimerThread已终止.
* @throws NullPointerException 如果 {@code task} is null
*/
public void scheduleAtFixedRate(TimerTask task, long delay, long period) {
if (delay < 0)
//如果delay为负数,抛出IllegalArgumentException异常
throw new IllegalArgumentException("Negative delay.");
if (period <= 0)
//如果period为负数,抛出IllegalArgumentException异常
throw new IllegalArgumentException("Non-positive period.");
//调用sched方法,传入要安排的任务,第一次执行任务的时间,和下一次执行的周期时间,sched方法下面进行介绍
sched(task, System.currentTimeMillis()+delay, period);
}
/**
* 从指定的时间开始执行,为重复的固定速率执行计划指定的任务。随后的发生以大约固定的间隔进行
* 间隔由指定的时间段隔开,即当前任务执行时间executionTime = task.nextExecutionTime;
* 则当前任务的下一次执行时间为executionTime + delay.
*
* 在固定速率执行中,每个执行都是相对于初始执行的计划执行时间。如果执行是
* 由于任何原因(如垃圾收集或其他后台活动)而延迟,两个或多个执行将快速连续发生以“赶上”。
* 从长远来看,执行频率将正好是指定周期的倒数(假设系统时钟在底层Object.wait(Long)是准确的.
* 以上结果,如果计划的第一次是在过去,
* 然后,任何“错过”的执行都将被安排为立即执行“追赶”。
*
* <p>固定速率执行适用于经常性活动对绝对时间敏感,例如每一小时一小时,或每天在特定时间。它也适用于经常性活动
* 其中执行固定数量的执行的总时间是重要的,例如每秒计时一次的倒计时十秒钟。最后,固定速率执行适合于
* 调度多个必须保持彼此同步的重复计时器任务。
*
* @param task 要安排的任务.
* @param firstTime 第一次执行任务的时间.
* @param period 重复任务重新执行之间的时间间隔,间隔时间(毫秒).
* @throws IllegalArgumentException 如果 {@code firstTime.getTime() < 0} 或者
* {@code period <= 0}
* @throws IllegalStateException 如果任务已经被安排或者任务已经取消
* , 或Timer已经取消, 或者 TimerThread已终止.
* @throws NullPointerException 如果 {@code task} 或者 {@code firstTime} is null
*/
public void scheduleAtFixedRate(TimerTask task, Date firstTime,
long period) {
if (period <= 0)
//如果period为负数,抛出IllegalArgumentException异常
throw new IllegalArgumentException("Non-positive period.");
//调用sched方法,传入要安排的任务,第一次执行任务的时间,和下一次执行的周期时间,sched方法下面进行介绍
sched(task, firstTime.getTime(), period);
}
/**
* 计划指定的任务在指定的时间执行和指定的时间段(毫秒),重复任务。如果period为正(fixed-rate)或者负(fixed-delay),
* 则任务被安排为重复执行;如果period为零,则任务被安排为一次性执行。
* 时间在中指定日期.getTime()格式。此方法检查计时器状态、任务状态和初始执行时间,
* 但不检查周期。
*
* @throws IllegalArgumentException 如果任务第一次执行时间<tt>time</tt>为负数.
* @throws IllegalStateException 如果任务已经被安排或者任务已经取消
* , 或Timer已经取消, 或者 TimerThread已终止.
* @throws NullPointerException 如果 {@code task} is null
*/
private void sched(TimerTask task, long time, long period) {
if (time < 0)
//如果任务第一次执行时间<tt>time</tt>为负数
throw new IllegalArgumentException("Illegal execution time.");
// 充分限制period的值,以防止数值溢出。
if (Math.abs(period) > (Long.MAX_VALUE >> 1))
period >>= 1;
//使用任务队列做为锁对象,当线程获取到queue锁对象,Timer中的
//cancel、purge方法、TimerThread中的run方法都不能马上执行,,需要等待获取到queue锁对象
synchronized(queue) {
if (!thread.newTasksMayBeScheduled)
//如果Timer已经取消, 或者 TimerThread已终止,抛出IllegalStateException异常
throw new IllegalStateException("Timer already cancelled.");
//使用TimerTask中的lock属性做为锁对象,防止任务在执行,其他线程调用TimerTask的cancel、scheduledExecutionTime方法
synchronized(task.lock) {
//如果新安排的任务不是新的任务,即任务的状态不是VIRGIN
if (task.state != TimerTask.VIRGIN)
//抛出IllegalStateException异常
throw new IllegalStateException(
"Task already scheduled or cancelled");
//任务的第一次执行时间
task.nextExecutionTime = time;
//任务的执行周期
task.period = period;
//将任务的状态修改为SCHEDULED表明此任务已被安排
task.state = TimerTask.SCHEDULED;
}
//将任务添加到任务队列中,添加到任务队列堆中,加到末尾位置,触发上堆化
queue.add(task);
//如果任务队列中的head task为新增的任务,表明任务队列中的执行时间最先执行(队列可能为空,或者新增的任务执行时间最早)
if (queue.getMin() == task)
//唤醒TimerThread线程
queue.notify();
}
}
/**
* 终止此Timer,放弃任何当前计划的任务.
* 不干扰当前正在执行的任务(如果存在)。Timer终止后,其执行线程TimerThread将优雅地终止
* ,没有更多的任务可以安排在它上.
*
* 请注意,从由该Timer调用的TimerTask的run方法中调用该方法绝对保证正在进行的任务执行是该Timer执行的最后一个任务执行.
* 此方法可以重复调用;第二次调用无效。
*/
public void cancel() {
//使用任务队列做为锁对象,当线程获取到queue锁对象,Timer中的
//sched、purge方法、TimerThread中的run方法都不能马上执行,需要等待获取到queue锁对象
synchronized(queue) {
//将TimerThread属性newTasksMayBeScheduled设置为false,表示此线程在任务队列中的任务执行
//完优雅退出,Timer不能往队列中添加任务,否则抛出IllegalStateException异常
thread.newTasksMayBeScheduled = false;
queue.clear(); //清除队列中的任务,清除这些过时的任务引用
queue.notify(); //通知TimerThread线程,防止TimerThread由于队列为空,等待
}
}
/**
* 从Timer的任务队列中删除所有取消的任务。调用此方法对Timer的行为没有影响,但是
* 从队列中删除对已取消任务的引用。如果没有对这些任务的外部引用,它们将符合垃圾收集条件。
* 大多数程序无需调用此方法。它是为少数取消大量任务的应用程序而设计的。
* 调用此方法将时间换空间:方法的运行时可能与n+clog(n)成正比,其中n是队列中的任务数,
* c是取消的任务数。请注意,允许从计划在此计时器上的任务中调用此方法.
*
* @return 从队列中删除的任务数.
* @since 1.5
*/
public int purge() {
//从队列中删除的任务数
int result = 0;
//使用任务队列做为锁对象,当线程获取到queue锁对象,Timer中的
//sched、cancel方法、TimerThread中的run方法都不能马上执行,需要等待获取到queue锁对象
synchronized(queue) {
for (int i = queue.size(); i > 0; i--) {
//判断每个任务队列中的任务状态是否是CANCELLED,如果是将任务从任务队列中移除,quickRemove方法不进行堆化,只是移除
if (queue.get(i).state == TimerTask.CANCELLED) {
//将任务从任务队列中移除
queue.quickRemove(i);
//移除的任务数加一
result++;
}
}
//如果要移除的任务数大于0,表示有任务已经移除,heapify统一进行堆化
if (result != 0)
//调用队列的heapify()统一进行堆化,让队列存储结构满足堆的两个特性
queue.heapify();
}
//返回从队列中删除的任务数
return result;
}六、TimerTask
/**
* 一种任务,可以由Timer安排一次或重复执行.
*
* @author Josh Bloch
* @see Timer
* @since 1.3
*/
public abstract class TimerTask implements Runnable {
/**
* 此对象用于控制对TimerTask内部的访问.
*/
final Object lock = new Object();
/**
* 此任务的状态,从下面的常量中选择.
*/
int state = VIRGIN;
/**
* 尚未计划此任务,即TimerTask未加入到任务队列中
*/
static final int VIRGIN = 0;
/**
* 计划执行此任务。如果是不重复的任务,它还没有被执行。重复任务,加入到队列中的状态为SCHEDULED
* 重复任务被执行状态也为SCHEDULED
*/
static final int SCHEDULED = 1;
/**
* 此非重复任务已执行(或当前正在执行正在执行)并且尚未取消.
*/
static final int EXECUTED = 2;
/**
* 此任务已取消(通过调用时间任务取消).
*/
static final int CANCELLED = 3;
/**
* 此任务的下一次执行时间,假设此任务计划为执行重复任务,
* 此字段在每次任务执行之前更新.
*/
long nextExecutionTime;
/**
* 重复任务的周期(毫秒)。正值表示
* 固定速率执行。负值表示固定延迟执行。
* 值0表示不重复的任务.
*/
long period = 0;
/**
* 创建新的TimerTask.
*/
protected TimerTask() {
}
/**
* 此TimerTask要执行的操作.
*/
public abstract void run();
/**
* 取消此TimerTask任务。如果任务是一次性安排的执行,但尚未运行或尚未计划,它将
* 不会再执行。如果任务已安排重复执行,则
* 不会再执行了。(如果此调用发生时任务正在运行,
* 任务将运行到完成,但不会再运行。)
* 注意,从重复的TimerTask绝对保证TimerTask将不跑再来一次。
* 这个方法可以重复调用;第二次调用和随后的调用不起作用。
*
* @return true 如果此任务计划为一次性执行且尚未运行,或者此任务计划为重复执行。
* 如果任务计划为一次性执行且已运行,或者如果任务从未计划,或者任务已取消,
* 则返回false。(松散地说,如果此方法阻止一个或多个计划执行,则返回<tt>true</tt>。
*/
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);
}
}
}