今天在系统定时任务中发现了一个问题,一旦修改系统时间,把系统时间调到当前时间之后(即大于当前时间T1)T2,Timer线程正常执行;如果再将系统时间修改到当前时间之前T3(即T3小于T2),那么Timer线程就会挂起,或者假死,此时不会再执行定时任务,好像线程已经死掉一样。
我们可以从Timer的实现源码中找下原因,从schedule方法进去,一直追踪调试下去,可以找到:
public void run() { try { mainLoop(); } finally { // Someone killed this Thread, behave as if Timer cancelled synchronized(queue) { newTasksMayBeScheduled = false; queue.clear(); // Eliminate obsolete references } } }
Timer的实现原理原来就是在一个线程中执行mainLoop()函数,再看下mainLoop()函数源码:
/** * 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) { } } }
代码中出现了while(true),说明一直在重复地执行,可以猜测我们的任务代码就是放在while(true)里面执行的。确定是这样,我们的任务被放在了一个queue的队列中,mainLoop方法就是不断地执行队列里面所有的任务。
//获取当前系统时间 currentTime = System.currentTimeMillis(); //下一次执行任务的时间 executionTime = task.nextExecutionTime; taskFired = (executionTime<=currentTime)
如果下一次执行任务的时间小于等于当前时间,证明执行任务的时间已经到了,向下执行任务,如果不是,即taskFired=false,那么就等待,等待的时间为executionTime - currentTime:
if (!taskFired) // Task hasn't yet fired; wait queue.wait(executionTime - currentTime);
如果我们把系统时间调整为未来的时间,那么executionTime<=currentTime肯定为true,执行下次任务,程序运行正常;
如果我们再次调整系统时间为之前的时间,那么此时executionTime<=currentTime肯定为false,那么就会执行代码queue.wait(executionTime - currentTime),此时线程就会挂起了,至于等待的时间明显为executionTime - currentTime。
处理最好的这个方法就是重启Tomcat ,Emmmmmmm,感觉方法有点烂,暂时只有这一个,emmmmm