这篇文章会从实现的原理和源码介绍一下Timer类是如何实现延时队列的。
实现原理
首先我们先不看源码,先介绍一下Timer实现延时任务的逻辑
- 我们在实例化Timer类的时候,Timer内部默认初始化一个TimerThread的线程
- 然后给这个线程初始化一个TaskQueue,TaskQueue内部维护者一个TimerTask的集合,也就是TimerTask[]
- 实例化的时候,Timer会在构造函数中,调用TimerThread的start()方法,直接启动线程
- 启动线程以后,会检查TaskQueue是否为空以及判断是否能继续添加任务,如果为空且可以添加任务,则调用TaskQueue的wait()方法,阻塞掉TimerThread
- 当给TaskQueue添加值的时候,会调用TaskQueue.notify()方法,唤醒TimerThread线程
- TimerThread根据传入的延迟时间,通过调用TaskQueue.wate(time),来实现延时执行task
上面就是Timer的实例化流程,那么Timer实现延迟队列的核心逻辑就是,Timer在实例化的时候,先启动一个TimerThread线程,线程内部自循环;通过TaskQueue是否有值,判断TimerThread是否需要等待;给TaskQueue添加值的时候,唤醒TimerThread,让它去执行TaskQueue中最早的task
Timer内部的主要属性
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 final TaskQueue queue = new TaskQueue();
/**
* The timer thread.
*/
// 默认创建的线程
private final TimerThread thread = new TimerThread(queue);
// 余下代码省略
// ......
}
Timer的构造函数
public Timer() {
this("Timer-" + serialNumber());
}
public Timer(String name) {
thread.setName(name);
thread.start();
}
从上面可以看到,直接调用了thread.start()方法启动一个线程,那么启动了线程以后,当然就是执行线程中的run方法,我们下面看TimerThread的run方法
TimerThread#run()
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()
下面解释一下主要的执行逻辑,会用*作为标记
private void mainLoop() {
while (true) {
try {
TimerTask task;
boolean taskFired;
synchronized(queue) {
// Wait for queue to become non-empty
// *作者的注释说的很明显了,等待queue队列不为空
while (queue.isEmpty() && newTasksMayBeScheduled)
// *到这里线程就阻塞掉了,需要等待唤醒
queue.wait();
if (queue.isEmpty())
break; // Queue is empty and will forever remain; die
// Queue nonempty; look at first evt and do the right thing
// *下面的逻辑就是queue不为空时的操作
long currentTime, executionTime;
task = queue.getMin();
synchronized(task.lock) {
// *去掉TimerTask.CANCELLED状态的任务
if (task.state == TimerTask.CANCELLED) {
queue.removeMin();
continue; // No action required, poll queue again
}
// *当前时间和任务执行时间,每一个task被加入queue的时候,都是根据System.currentTimeMillis()+time生成的
// *task.nextExecutionTime = System.currentTimeMillis()+time
currentTime = System.currentTimeMillis();
executionTime = task.nextExecutionTime;
// *还没有到执行的时间
if (taskFired = (executionTime<=currentTime)) {
// *task.period代表循环执行的延时时间如果为0就只执行一次
// 可以理解为任务执行一次以后,等待task.period毫秒后再执行一次,反复循环
if (task.period == 0) { // Non-repeating, remove
queue.removeMin();
task.state = TimerTask.EXECUTED;
} else { // Repeating task, reschedule
queue.rescheduleMin(
task.period<0 ? currentTime - task.period
: executionTime + task.period);
}
}
}
// *这里就是延迟执行的逻辑,其实就是wati(对应的时间)
//
if (!taskFired) // Task hasn't yet fired; wait
queue.wait(executionTime - currentTime);
}
if (taskFired) // Task fired; run it, holding no locks
// *执行自己定义的TimerTask
task.run();
} catch(InterruptedException e) {
}
}
}
}
上面的代码,我们看到了核心逻辑是queue.wait(),先判断queue是否为空,为空的话等待;再判断是否到了执行时间,没到的话queue.wait(executionTime - currentTime)等待时间到。那么问题来了,第一次queue为空的等待,是怎么被唤醒的?
Timer#schedule()
我们使用Tiemr的时候,一般是先创建一个Timer,再添加一个TimerTasker那么唤醒的逻辑,就在schedule()里面
先看主流程:
public class TimerTaskTest {
public static void main(String[] args) {
//定义一个TimerTask
TimerTask timerTask = new TimerTask() {
/**
* The action to be performed by this timer task.
*/
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " TimerTask is running");
}
};
// 创建一个Timer
Timer timer = new Timer();
// 调用schedule
timer.schedule(timerTask, 1000);
}
}
我们知道new Timer()的时候,就有一个TimerThread被创建了,并且在等待queue不为空;schedule()就是把任务加入queue,并且唤醒TimerThread
public void schedule(TimerTask task, long delay) {
if (delay < 0)
throw new IllegalArgumentException("Negative delay.");
sched(task, System.currentTimeMillis()+delay, 0);
}
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.");
// *设置TimerTask的属性,让TimerThread调用的时候使用
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;
}
// *添加task
queue.add(task);
if (queue.getMin() == task)
// 唤醒TimerThread继续执行
queue.notify();
}
}
看完核心逻辑以后,是不是发现一个问题,这个Timer无法优雅的cancel,没发配置queue执行完以后,自行cancel
坑
Timer 类本身在Java 5及之后的版本已经被推荐使用 ScheduledExecutorService 替代,因为 ScheduledExecutorService 提供了更灵活的调度和取消任务的机制,同时支持更多的任务执行策略。所以只能学习一下上面的思想,真的用的话,还是用ScheduledExecutorService吧