Timer是jdk提供的定时任务执行器.
我们本文主要分析一下,Timer的工作原理.
一、Timer的使用方法
1.1.代码
package com.study.jdk.sched;
import java.util.Timer;
import java.util.TimerTask;
public class TimerTest {
public static void main(String[] args) {
Timer timer = new Timer();
long begin = System.currentTimeMillis();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("执行定时逻辑:"+(System.currentTimeMillis()-begin));
}
},1000,3000);
}
}
结果:
执行定时逻辑:1002
执行定时逻辑:4006
执行定时逻辑:7011
执行定时逻辑:10014
执行定时逻辑:13018
执行定时逻辑:16022
执行定时逻辑:19027
执行定时逻辑:22031
执行定时逻辑:25034
执行定时逻辑:28039
执行定时逻辑:31043
执行定时逻辑:34047
执行定时逻辑:37052
二、Timer的原理和源码分析
2.1.原理图
Timer原理:
- Timer内部维护了一个优先队列,存储提交的TimerTask,TimerTask即用户要执行的定时逻辑,实现了Runnable接口
- 在创建一个Timer的时候,会创建一个TimerThread,可以设置以守护线程方式运行
- ThreadThread 以while(true)的方式,从优先队列queue中获取TimerTask,如果是周期性的,再重新将设置当前TimeTask的时间,优先队列执行fix逻辑,重新设置task位置
- 执行当前任务
2.2.代码分析
2.2.1.添加任务
Timer:
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.");
// 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.");
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);
if (queue.getMin() == task)
queue.notify();
}
}
sched方法流程:
- 排他占有当前队列,这说明,在添加任务期间,无法从queue获取元素,即无法执行其他任务
- 判断当前Timer是否可用
- 获取当前任务的锁
- 判断当前任务状态,如果已经在调度状态或者取消,就不能添加,即添加过一次,就不能再添加
- 设置下次执行的时间配置
- 下次执行时间
- 周期时间(如果是周期执行的话)
- 任务状态,设置为SCHEDULED
- 设置下次执行的时间配置
- 添加到queue中
- 如果当前任务在是第一个待执行的,唤醒queue后面会说下,为什么要执行这一步唤醒
2.2.2.执行任务
Timer.TimerThread
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;
TimerThread(TaskQueue queue) {
this.queue = 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
}
}
}
/**
* The main timer loop. (See class comment.)
*/
private void mainLoop() {
while (true) {
try {
TimerTask task;
boolean taskFired;
synchronized(queue) {
// Wait for queue to become non-empty
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);
}
}
}
if (!taskFired) // Task hasn't yet fired; wait
queue.wait(executionTime - currentTime);
}
if (taskFired) // Task fired; run it, holding no locks
task.run();
} catch(InterruptedException e) {
}
}
}
}
public void cancel() {
synchronized(queue) {
thread.newTasksMayBeScheduled = false;
queue.clear();
queue.notify(); // In case queue was already empty.
}
}
执行TimerTask的逻辑主要在mainLoop里面:
-
首先排他占有queue
-
第一个等待
- 当前任务队列为空和newTasksMayBeScheduled,该值默认为true
- 上面添加完任务,执行queue.notify,可能唤醒的是这个地方,如果当前当前任务是第一个添加的
-
接下来,再次判断queue是否为空,如果为空,结束循环.因为这时候newTasksMayBeScheduled已经被设置为false了,
- 当执行Timer.cancel方法时newTasksMayBeScheduled会被设置成false,并将当前队列清空,
-
获取下一个需要执行的task,即优先队列的第一个元素
-
获取当前task的锁
- 判断当前task状态,如果task已经被取消,从队列中移除,循环执行第一步操作
- 获取当前时间戳:currentTime,获取task执行的时间戳nextExecutionTime
- 如果nextExecutionTime 小于currentTime,则说明,当前任务需要立刻执行
- 如果当前任务不是周期性的,从队列移除
- 否则: 重新设置下次任务执行时间,加入优先队列queue
- 判断是否立刻执行
- 是:执行定时任务逻辑
- 否:queue.wait
- 如果在这里wait,当有新任务加入,并且新任务比该任务要提前执行,说明新加入的任务已经位于优先队列一个元素.
- 在上面的sched方法中,会执行queue.notify方法,唤醒锁,则该处的queue.wait方法,会被唤醒,继续执行while循环,获取第一个task,并执行它
我们从mainLoop方法,可以发现该逻辑有点想生产者-消费者模型:
- sched:增加新的任务
- mainLoop:消费任务
- 每次执行循环,都取优先队列,第一个任务.
三、注意事项
我们从上面代码分析可以得到以下信息:
- Timer执行定时逻辑是单线程的
- 如果一个定时逻辑TimerTask执行时间过长,会阻塞后面的执行,所以一定要避免,TimerTask里面的逻辑不会长时间阻塞
- 调用Timer.cancel会清空当前所有待执行的task,并关闭timer.