关于java.util.Timer timer定时器如何触发
看看就好:在一次任务需求中,需求是每周固定时间点触发一次任务,当时用定时器实现,在需求发版后一直都是没问题的,突然某个星期本地的定时任务并没有触发,我便非常诧异害怕出现了什么BUG,便去测试定时器,发现不论怎么操作定时任务都没有触发。
步入正题:定时器是如何实现的?
实现定时器,便要先定义一个定时器,
//定义定时器
private java.util.Timer timer = new java.util.Timer(true);
此时我们还需要触发的定时任务:
//安排指定的任务在指定的时间开始进行重复的固定速率执行.
timer.schedule(TimerTask task,Date firstTime,long period)
其中schedule有很多重载方法,例如:
//安排在制定的时间执行指定的任务。
(1)Timer.schedule(TimerTask task,Date time)
//安排指定的任务在指定的时间开始进行重复的固定延迟执行.
(2)Timer.schedule(TimerTask task,Date firstTime ,long period)
//安排在指定延迟后执行指定的任务.
(3)Timer.schedule(TimerTask task,long delay)
//安排指定的任务从指定的延迟后开始进行重复的固定延迟执行.
(4)Timer.schedule(TimerTask task,long delay,long period)
//安排指定的任务在指定的时间开始进行重复的固定速率执行.
(5)Timer.scheduleAtFixedRate(TimerTask task,Date firstTime,long period)
//安排指定的任务在指定的延迟后开始进行重复的固定速率执行
(6)Timer.scheduleAtFixedRate(TimerTask task,long delay,long period).
解析定义定时器代码
//定义定时器
private java.util.Timer timer = new java.util.Timer(true);
此时我们进入Timer构造方法,
public Timer(String name, boolean isDaemon) {
thread.setName(name);
thread.setDaemon(isDaemon);
thread.start();
}
发现 thread 是Timer类的一个私有的成员变量
private TaskQueue queue = new TaskQueue();
private TimerThread thread = new TimerThread(queue);
我们再往下看,进入TimerThread类
class TimerThread extends Thread {
boolean newTasksMayBeScheduled = true;
private TaskQueue queue;
TimerThread(TaskQueue queue) {
this.queue = queue;
}
public void run() {
try {
mainLoop();
} finally {
synchronized(queue) {
newTasksMayBeScheduled = false;
queue.clear(); // Eliminate obsolete references
}
}
}
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;
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) {
}
}
}
}
TimerThread类继承了Thread,上面thread.start();开启了线程则会执行 run方法,则会执行mainLoop方法,分析一下这个方法:
先是while (true)
无限循环,然后定义了一个定时任务TimerTask
,再定义了一个默认值为false的标识taskFired
,然后同步判断queue
是否为空,为空则等待。此时我们只定义了定时器,所以里面的任务列表时空的,线程被挂起。
此时执行到timer.schedule(TimerTask task,Date firstTime,long period)
我们进入schedule方法:
private void sched(TimerTask task, long time, long period) {
if (time < 0)
throw new IllegalArgumentException("Illegal execution time.");
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();
}
}
其中将time时间赋值给任务的下一次执行时间nextExecutionTime
,任务状态设为此任务还未被执行,将要执行
,间隔赋给period
,向任务队列添加任务queue.add(task)
,if (queue.getMin() == task)
这个的意思是如果队列第一个任务是当前任务的话,唤醒队列。
唤醒队列后我们再来看mainLoop
方法,定义了两个时间long currentTime, executionTime;
一个是当前时间一个是执行时间。currentTime = System.currentTimeMillis();
当前时间等于计算机系统时间,executionTime = task.nextExecutionTime;
执行时间等于我们传入的下一次执行时间。if (taskFired = (executionTime<=currentTime)) {
如果下一次执行时间小于当前时间,则更新下一次执行时间,task.run();
run方法作为普通方法调用一次。如果下一次执行时间大于当前时间则queue.wait(executionTime - currentTime);
等待相差时间执行,
揭晓答案:上面的源码中可以看到,此定时器所用来比较的时间是当前系统时间,也就是他会获取本地服务器的时间,当时我本地tomcat的系统时间错乱了🤪,导致本地调试时到我设定的时间时触发不了。