E81086713E446D36F62B2AA2A3502B5EB155

Java杂家

杂七杂八。。。一家之言

BlogJava 首页 新随笔 联系 聚合 管理
  141 Posts :: 1 Stories :: 174 Comments :: 0 Trackbacks
先看个小测试程序:
package test;

import java.util.Timer;
import java.util.TimerTask;

public class TestTimer extends TimerTask{

    
int number;
    
/**
     * 
@param args
     
*/
    
public static void main(String[] args) {
        Timer timer
=new Timer();
        timer.schedule(
new TestTimer(), 1000, 1000);
        
    }

    
public void run() {
       System.out.println(
"number="+(++number));
        
    }

}
正常情况该程序每秒打印当前的 number值。
现在我们来改变这个正常情况:
1)保持程序运行
2)通过系统时间栏把系统时间往前调一天。

过一会儿,你会发现该程序停止输出了?

对,要看到下一个输出,你得等一天了,在这种情况下,你即使现在重新调整到正确系统时间,你仍然得等到下一天才能看到输出。

为什么呢?
下面是JDK中Timer调度的核心代码(在原有注释的基础上加了一些):
 /**
     * The main timer loop.  (See class comment.)
     
*/
    
private void mainLoop() {
        
while (true) {
            
try {
                
//to be scheduled task
                TimerTask task;
                
//fired one 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) {
                            
//find the first task from the queue.
                            queue.removeMin();
                            
continue;  // No action required, poll queue again
                        }
                        
//here ,use system current time to determin whether or not fire a task
                        currentTime = System.currentTimeMillis();
                        executionTime 
= task.nextExecutionTime;
                        
//So when we change system time to long ago,this expression will be evaluate to false
                        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) {
            }
        }
    }
那么,我们如何能让Timer调度不依赖这种系统时间呢?
Windows API中GetTickCount()返回系统自启动以来的毫秒数。我们可以用这个来替代System.currentTimeMillis().这样即使改变系统时间
Timer的调度也不会所影响。

改完后再来测试一把
public class TestTimer2 extends com.yovn.labs.util.TimerTask{
    
int number;
    
/**
     * 
@param args
     
*/
    
public static void main(String[] args) {
        com.yovn.labs.util.Timer timer
=new com.yovn.labs.util.Timer();
        timer.schedule(
new TestTimer2(), 10001000);
        
    }

    
public void run() {
       System.out.println(
"number="+(++number));
        
    }

}

这时的输出没有再受系统时间的改变了。

本文代码下载:
source code

posted on 2007-03-17 10:40 DoubleH 阅读(2167) 评论(3)  编辑  收藏

Feedback

# re: Fix JDK Timer的bug(确认是个bug,见http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4290274) 2007-03-17 12:45 BeanSoft
看了看那个 Bug 页, 貌似 JDK 1.5 也没 Fix. Submit Date 11-NOV-1999. 所以我觉得要 Fix 就必须每个操作系统都提供系统启动时钟的功能, 但是你的这个就会引入另一个 Bug, 如果系统重启, 然后启动时钟就会被重置(我们假设应用存储状态后继续执行), 依赖这种做法的定时器也会失效. 所以我觉得 Sun 没有按照这种想法来 Fix 第一是不是所有系统都提供系统启动时间(典型的嵌入式设备)的API, 第二是这种做法又会引入新的问题. 一般来说依赖当前时间做定时是个比较简单实用的思路, Windows 软件防止过期后继续使用也是在注册表里记录第一次使用的时间, 以后如果发现时间超前就会判定为超出试用期.

JDK 必须要为不同的平台提供一致的特性, 所以这个 Bug 改起来还真是有点困难. 不过添加一个能获取系统启动时间的 API 倒是值得考虑(部分平台可用).

发现刚发的评论不小心改掉了, 我说的是服务器在生产环境下不中断应用这么大的时间改动很危险, 不现实.  回复  更多评论
  

# re: Fix JDK Timer的bug(确认是个bug,见http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4290274) 2007-03-17 13:04 Javacap
@BeanSoft
用户可能误操作,改完以后再改回来也是不行的。另外也不一定要这么大的时间改变。改一分钟,就会导致延迟一分钟。假如用户使用scheduleAtFixRate,则本来期望一段时间内执行N次,而实际上少执行了几次,这是不可接受的。

在Linux下,GetTickCount()可以用times()函数来实现。
#include <sys/times.h>

long GetTickCount()
{
struct tms tm;
return times(&tm);
}

  回复  更多评论
  

# re: Fix JDK Timer的bug(http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4290274) 2007-03-17 18:38 喜来乐哈哈
选择SWT的话,用Display.timerExec (int milliseconds, Runnable runnable)就不会有这个问题. 它是调用系统原生的timer功能,Windows下SetTimer()函数, gtk下用gtk_timeout_add()函数.   回复  更多评论
  


只有注册用户登录后才能发表评论。


网站导航: