定时器Timer类是一个最基础的定时调度任务的类。在日常开发中经常会遇到。
这个类的参数有以下几个:
(1)定时器任务队列,上面的定时器线程就是从这个队列中取任务来执行。有新任务需要加入也是放到这个队列中。
private final TaskQueue queue = new TaskQueue();
(2)定时器线程,负责调度定时器任务。
private final TimerThread thread = new TimerThread(queue);
默认的构造函数有以下4个:
(1)无参数构造函数
Timer() { ( serialNumber()); }
这个构造函数调用了另一个构造函数,从而给线程设置一个默认的名字。这个名字就是Timer-加上一个serialNumber(); 我们来看看这个serialNumber()是什么鬼?
nextSerialNumber (); serialNumber() { nextSerialNumbergetAndIncrement(); }
原来这个serialNumber就是一个自增的数据,这个调用了AtomicInteger类来实现技术的。 这个设置为静态,说明我们在用户代码中假如创建多个定时器,也能保证每个定时器能得到一个独一无二的、递增的序列号。
(2)这个构造函数可以用户自定义线程名称。
public Timer(String name) { thread.setName(name); thread.start(); }
(3)这个构造函数使用默认的名字和设置守护线。
Timer( isDaemon) { ( serialNumber(), isDaemon); }
(4)这个构造函数可以设置名字和设置守护线程.
Timer( name, isDaemon) { threadsetName(name); threadsetDaemon(isDaemon); threadstart(); }
小结:创建了定时器对象,在构造函数中会帮我们启动线程的。接下来就是我们调用schedule函数来调度任务。
接着,我们来看一下schedule这个方法,有哪些方法和参数。
(1)这个方法就是提交一个任务,并告诉线程多久以后执行。只执行一次任务。
schedule( task, delay) { (delay ) (); sched(task, currentTimeMillis()delay, ); }
(2)这个方法提交一个任务,并指定什么日期执行,只执行一次任务。
public void schedule(TimerTask task, Date time) { sched(task, time.getTime(), 0); }
(3)这个方法提交一个任务,并指定延长delay时间执行,周期是period。
schedule( task, delay, period) { (delay ) (); (period ) (); sched(task, currentTimeMillis()delay, period); }
(4)这个方法和上面的类似,只是传递的是日期。
schedule( task, firstTime, period){ (period ) (); sched(task, firstTimegetTime(), period); }
同样的,Timer类还提供了两个scheduleAtFixedRate方法。 区别在于,schedule 下一次的执行时间点=上一次程序执行完成的时间点+间隔时间. 而scheduleAtFixedRate 下一次的执行时间点=上一次程序执行完成的开始时间点+间隔时间. 我们看scheduleAtFixedRate的方法可以看出,传递第三个参数有正反分。
scheduleAtFixedRate( task, delay, period) { (delay ) (); (period ) (); sched(task, currentTimeMillis()delay, period); }
小结:Timer类提供了定时调度任务的schedule和scheduleAtFixedRate方法,有点区别。区别在于一个以上一次任务完成时间作为起点,一个以起始时间作为起点。
接下来我们看看这个TimerTask类,看看这个类有啥特殊的?这个TimerThread是一个抽象的类,这个类继承了Thread类。其中抽象方法为:
public abstract void run();
我们看看这个类中还有其他什么?通过下面几个变量标识线程的不同工作状态,默认值为0。
state ; ; ; ; ;
这个类中还有两个和调度相关的变量:
long nextExecutionTime;//下一次任务执行时间
long period = 0; //任务调度周期,也就是每多长时间调度一次 我们看看这个类中还有个取消的方法,这个方法只是将线程状态标识为cannel状态。
cancel() { (lock) { result (state ); state ; result; } }
下面是定时器调度算法的核心:
接着,我们来看看TimerThread中的run方法。
run() { { mainLoop(); } { (queue) { newTasksMayBeScheduled ; queueclear(); } } }
这里,我们重点分析mainLoop方法,这个是定时器调度任务的基础。
小结:在我们创建Timer对象的时候,其实就启动了TimerThread线程。在我们调用schedule或者scheduleAtFixedRate时候, 我们将我们的任务(也就是Runnable对象)包装成TimerTask对象,以及延迟参数和周期参数传递进task队列中。 这样TimerThread在执行任务的时候拿到这个任务的这些参数,从而判断是一次性任务还是周期性任务(通过period是否为0), 然后继续将任务添加到Queue中。每次去Queue中去任务的时候,都是取最小的任务,也就是离当前时间最近的任务进行处理。