Java定时任务 - Timer 原理
概要
Jdk库自带有两种实现定时任务的技术。一种是通过Timer,另外一个是通过ScheduledThreadPoolExecutor。下面为大家分析Timer实现的原理。
一、Timer
1、Timer使用
public class TimerTest extends TimerTask {
@Override
public void run() {
System.out.println("test1 --------- " + Thread.currentThread());
}
static class TimerDemo {
public static void main(String[] args) {
Timer timer = new Timer();
// 指定三秒后执行TimerTest中的run方法,每间隔1秒执行一次
timer.schedule(new TimerTest(), 3000, 1000);
}
}
}
2、源码分析
看到上面的实践后是不是觉得很简单?接着我们分析下上面的代码。
首先要注意TimerTest这个类继承了TimerTask
TimerTask
public abstract class TimerTask implements Runnable {
/**
* This object is used to control access to the TimerTask internals.
*/
final Object lock = new Object();
/**
* The state of this task, chosen from the constants below.
*/
int state = VIRGIN;
/**
* This task has not yet been scheduled.
*/
static final int VIRGIN = 0;
/**
* This task is scheduled for execution. If it is a non-repeating task,
* it has not yet been executed.
*/
static final int SCHEDULED = 1;
/**
* This non-repeating task has already executed (or is currently
* executing) and has not been cancelled.
*/
static final int EXECUTED = 2;
/**
* This task has been cancelled (with a call to TimerTask.cancel).
*/
static final int CANCELLED = 3;
/**
* Next execution time for this task in the format returned by
* System.currentTimeMillis, assuming this task is scheduled for execution.
* For repeating tasks, this field is updated prior to each task execution.
*/
long nextExecutionTime;
/**
* Period in milliseconds for repeating tasks. A positive value indicates
* fixed-rate execution. A negative value indicates fixed-delay execution.
* A value of 0 indicates a non-repeating task.
*/
long period = 0;
/**
* Creates a new timer task.
*/
protected TimerTask() {
}
/**
* The action to be performed by this timer task.
*/
public abstract void run();
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);
}
}
}
去掉注释后,这个类的代码也就几十行。你可以看到TimerTask实现了Runnable接口,有一个抽象的 run() 方法,我们的TimerTest继承并实现了 run() 方法。以及这个类有几个成员变量:
lock
:用来线程加锁
state
:表示任务的状态
nextExecutionTime
:下一次执行任务的时间
period
:执行任务的周期
而它只有两个方法 :
cancel()
:取消任务,改变任务的state
scheduledExecutionTime()
:设置下次要执行的时间
Timer
我们在来分析下Timer类,先附上一张思维导图,方便记忆理解
下面简单列举Timer关键代码,具体代码可以自己去查看
public class Timer {
/** 用来存放要执行的任务,通过最小堆维护的队列 **/
private final TaskQueue queue = new TaskQueue();
/** 负责执行任务的线程 **/
private final TimerThread thread = new TimerThread(queue);
/** 构造方法 设置了线程名称 是否为守护进程 最后启动了这个线程 **/
public Timer(String name, boolean isDaemon) {
thread.setName(name);
thread.setDaemon(isDaemon);
thread.start();
}
}
在来看看TimerThread这个类
TimerThread
class TimerThread extends Thread {
boolean newTasksMayBeScheduled = true;
private TaskQueue 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
}
}
}
/** 关键代码 执行定时任务 **/
private void mainLoop() {
while (true) {
try {
TimerTask task;
boolean taskFired;
synchronized(queue) {
// 如果队列为空,且newTasksMayBeScheduled为true 则线程进入等待状态
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
long currentTime, executionTime;
// 获取队列中最早要执行的任务
task = queue.getMin();
synchronized(task.lock) {
if (task.state == TimerTask.CANCELLED) {
// 如果已经是取消状态,则将任务从队列中删除
queue.removeMin();
continue; // No action required, poll queue again
}
// 获取当前时间
currentTime = System.currentTimeMillis();
// 获取下个任务要执行的时间
executionTime = task.nextExecutionTime;
// 如果任务执行时间小于等于当前时间则开始执行
if (taskFired = (executionTime<=currentTime)) {
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);
}
}
}
// Task hasn't yet fired; wait
if (!taskFired)
queue.wait(executionTime - currentTime);
}
//如果任务的执行时间到了,就执行这个任务
if (taskFired)
task.run();
} catch(InterruptedException e) {
}
}
}
}
我们已经大概知道TimerThread是怎么循环执行定时任务的了,接下来看看Timer的schedule()
以及sched()
方法
schedule & sched
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);
}
private void sched(TimerTask task, long time, long period) {
if (time < 0)
throw new IllegalArgumentException("Illegal execution time.");
// 获取执行周期 period 的绝对值,如果大于 二分之一的 Long.MAX_VALUE, 则将 period 除以二
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);
// 如果任务加入队列后排在堆顶,则直接唤醒线程执行任务 (还记得之前TimerThread中让线程等待吗?)
if (queue.getMin() == task)
queue.notify();
}
}
3、总结
在我们创建Timer时候就会初始化并启动一条线程,用来执行任务,并初始化一个优先队列,使用的是最小堆数据结构,将最早执行的任务放在堆顶。
当我们调用 schedule() 方法时候,就是将任务放进优先队列中。TimerThread循环判断是否已经达到执行的时间,如果到了先计算出下次执行的时间以及调整堆,最后在执行任务,如果没到则线程进入等待。