热爱技术,追求卓越
不断求索,精益求精

java.util.Timer使用详解及注意事项

对于java来说,最简单的定时任务可以使用jdk自带的java.util.Timer来实现。java.util.Timer类是通过调度一个java.util.TimerTask的任务并让这个任务依照某种频度执行一次或重复执行。

java.util.Timer

从java.util.Timer的源码可以看到,Timer类定义了两个私有变量queue(java.util.TaskQueue类型)和thread(java.util.TimerThread.TimerThread类型)。

public class Timer {
    /**
     * The timer task queue.  This data structure is shared with the timer
     * thread.  The timer produces tasks, via its various schedule calls,
     * and the timer thread consumes, executing timer tasks as appropriate,
     * and removing them from the queue when they're obsolete.
     */
    private TaskQueue queue = new TaskQueue();

    /**
     * The timer thread.
     */
    private TimerThread thread = new TimerThread(queue);

java.util.TaskQueue

对于Timer的私有变量queue,是java.util.TaskQueue类型的。TaskQueue是一个定时器任务队列,一个TimerTask的优先级队列。

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.
     */
    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;

java.util.TimerThread

对于Timer的私有变量thread,类型是继承自Thread类的java.util.TimerThread类类型。这是Timer类的任务运行线程,参数是就是上面提到的java.util.TaskQueue类型的任务队列。

class TimerThread extends Thread {
    /**
     * This flag is set to false by the reaper to inform us that there
     * are no more live references to our Timer object.  Once this flag
     * is true and there are no more tasks in our queue, there is no
     * work left for us to do, so we terminate gracefully.  Note that
     * this field is protected by queue's monitor!
     */
    boolean newTasksMayBeScheduled = true;

    /**
     * Our Timer's queue.  We store this reference in preference to
     * a reference to the Timer so the reference graph remains acyclic.
     * Otherwise, the Timer would never be garbage-collected and this
     * thread would never go away.
     */
    private TaskQueue queue;

java.util.TimerTask

上面提到了java.util.TimerTask类,TimerTask类实现了Runnable接口,待运行的任务置于run()中。在构造定时任务的时候,从TimerTask继承并实现run方法。

public abstract class TimerTask implements Runnable {
    /**
     * This object is used to control access to the TimerTask internals.
     */
    final Object lock = new Object();

    //...

    /**
     * The action to be performed by this timer task.
     */
    public abstract void run();

工作原理

当Timer类型的对象调用schedule或scheduleAtFixedRate等方法时,把TimerTask类型的任务对象作为参数传递进去,从Timer的内部调用方法中sched能够看出,sched方法中须要操作TaskQueue队列把这个任务对象维护到Timerd的任务队列queue中,而TimerThread线程启动之后相同使用这个队列,为了保证线程的安全,使用synchronized来控制对queue的操作。

核心函数

  • void java.util.Timer.schedule(TimerTask task, long delay):多长时间(毫秒)后运行任务
  • void java.util.Timer.schedule(TimerTask task, Date time):设定某个时间运行任务
  • void java.util.Timer.schedule(TimerTask task, long delay, long period):delay时间后開始运行任务,并每隔period时间调用任务一次。
  • void java.util.Timer.schedule(TimerTask task, Date firstTime, long period):第一次在指定firstTime时间点运行任务,之后每隔period时间调用任务一次。
  • void java.util.Timer.scheduleAtFixedRate(TimerTask task, long delay, long period):delay时间后開始运行任务。并每隔period时间调用任务一次。
  • void java.util.Timer.scheduleAtFixedRate(TimerTask task, Date firstTime, long period):第一次在指定firstTime时间点运行任务。之后每隔period时间调用任务一次。
  • void java.util.Timer.cancel():终止该Timer
  • boolean java.util.TimerTask.cancel():终止该TimerTask

schedule和scheduleAtFixedRate的区别

schedule()方法更注重保持间隔时间的稳定:保障每隔period时间可调用一次。

scheduleAtFixedRate()方法更注重保持运行频率的稳定:保障多次调用的频率趋近于period时间。假设某一次调用时间大于period,下一次就会尽量小于period。以保障频率接近于period。

Timer的缺陷及注意事项

1、由于执行任务的线程只有一个,所以如果某个任务的执行时间过长,那么将破坏其他任务的定时精确性。如一个任务每1秒执行一次,而另一个任务执行一次需要5秒,那么如果是固定速率的任务,那么会在5秒这个任务执行完成后连续执行5次,而固定延迟的任务将丢失4次执行。

2、如果执行某个任务过程中抛出了异常,那么执行线程将会终止,导致Timer中的其他任务也不能再执行。

3、Timer使用的是绝对时间,即是某个时间点,所以它执行依赖系统的时间,如果系统时间修改了的话,将导致任务可能不会被执行。

一个简单的例子

该例子模拟了Timer缺陷中的第一种情况,可以切换schedule和scheduleAtFixedRate对比测试效果,其他缺陷可自行验证。

package cn.lovecto.test.timer;

import java.util.Timer;
import java.util.TimerTask;

public class TimerJob {

    private static Timer timer = new Timer(TimerJob.class.getName(), true);

    static {
        //延迟1000ms执行程序,每1000ms执行一次,此处了换成scheduleAtFixedRate进行对比
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                doSomething1();
            }
        }, 1000, 1000);
        //从当前时间执行首次,每1000执行一次,此处了换成scheduleAtFixedRate进行对比
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                doSomething2();
            }
        }, 1000, 5000);
    }

    private static void doSomething1() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("doSomething1");
    }

    private static void doSomething2() {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("doSomething2");
    }

    public static void main(String[] args) throws InterruptedException {
        //循环查看执行效果
        while (true) {
            Thread.sleep(100);
        }
    }
}

赞(2)
未经允许不得转载:LoveCTO » java.util.Timer使用详解及注意事项

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

热爱技术 追求卓越 精益求精