• 学习线程的时候,做了一个按秒显示当前时间的小程序。这个程序不难,
    但提到这个小程序有缺陷,就是你仔细盯着输出结果看,有时候会发现显示
    的时间突然跳了一秒,比如从19:56:42 跳到19:56:44。
    我对这个现象产生的原因很感兴趣,于是研究了一下它是如何产生的以及研究了一下解决办法。
    原程序如下:

    import java.text.SimpleDateFormat;
    import java.util.Calendar;
    import java.util.Date;

    public class ThreadDemo8 {

    /**
    * @param args
    */
    public static void main(String[] args) {
    /*
    * 实现电子表功能
    * 每秒钟输出依次当前系统时间
    * HH:mm:ss
    */
    Thread t=new Thread(){
    public void run(){
    Calendar calendar;
    Date date;
    String str;
    SimpleDateFormat sdf=new SimpleDateFormat("HH:mm:ss");
    for(;;){
    calendar=Calendar.getInstance();
    calendar.add(calendar.HOUR_OF_DAY, 16);
    calendar.add(calendar.MINUTE, 4);
    date=calendar.getTime();
    str=sdf.format(date);
    System.out.println(str+" ");//输出当前时间
    try{
    Thread.sleep(1000);
    }catch(InterruptedException e){
    }
    }
    }
    };
    t.start();
    }
    }

    为了使时间显示的更精确,我将用于输出当前时间的 System.out.println(str+" ");
    这句改为了 System.out.println(str+" "+System.currentTimeMillis()%1000);
    输出结果如下:
    20:09:43 998
    20:09:44 998
    20:09:45 998
    20:09:46 999
    20:09:47 999
    20:09:48 999
    20:09:49 999
    20:09:50 999
    20:09:52 0
    20:09:53 0
    20:09:54 0
    20:09:55 0
    20:09:56 1
    20:09:57 1
    20:09:58 1
    20:09:59 1
    20:10:00 2
    20:10:01 2
    20:10:02 2

    可以发现,在20:09:50到20:09:52发生了跳跃,把20:09:51隔过去了。还能发现大约每数4.4次,
    毫秒数会+1,也就是每4.4*1000=4400次即73分钟会跳一次秒。这是不是很像每天比实际地球自转
    时间多算约4分钟,所以每4年要多1天来抵消这个误差?这个计时程序的误差来自
    Thread.sleep(1000); 这里没有把计算机运行一次for循环所用的时间考虑进去。所以,它应
    该sleep更少的时间才对,大约少sleep 1/4.4毫秒。
    于是,我就像用增加润年的方法把
    Thread.sleep(1000); 改为:
    Thread.sleep(1000-i%5/4+i%40/39); //每5毫秒减少1毫秒,每40毫秒增加1毫秒
    它的输出结果变成这样了
    20:25:36 393
    20:25:37 393
    20:25:38 394
    20:25:39 394
    20:25:40 394
    20:25:41 393
    20:25:42 393
    20:25:43 394
    20:25:44 394
    20:25:45 394
    20:25:46 393
    20:25:47 394
    20:25:48 394
    误差小了很多,但是还是不够精确。就像我们现实的时间虽有润年调整,还是无法和地球自转时间对照,
    因此前几年有2天都出现了如19:59:60这样时间,增加1秒以对地球的计时做校正。
    但是,在这个程序中我们可以抛弃这种永远无法真正校准时间的方法。我想起了原来我学过的一门课程
    《自动控制原理》,里面有个概念叫做负反馈,根据结果来校正下一次的结果可以做到真正的精准。
    用在这里具体就是:输出的时间超了1毫秒就在下次减1毫秒,超2毫秒就减2毫秒。反正它能自动调整为
    准确时间
    于是有了如下方法:
    Thread.sleep(1000); 改为:
    Thread.sleep(1000-System.currentTimeMillis()%1000);
    输出结果如下:
    20:32:32 0
    20:32:33 0
    20:32:34 0
    20:32:35 0
    20:32:36 0
    20:32:37 0
    20:32:38 0
    可以发现,这次的输出时间都是和系统几乎同步的!做到了真正的精准。最终版本的程序如下:


    import java.text.SimpleDateFormat;
    import java.util.Calendar;
    import java.util.Date;

    public class ThreadDemo8 {

    /**
    * @param args
    */
    public static void main(String[] args) {
    /*
    * 实现电子表功能
    * 每秒钟输出依次当前系统时间
    * HH:mm:ss
    */
    Thread t=new Thread(){
    public void run(){
    Calendar calendar;
    Date date;
    String str;
    SimpleDateFormat sdf=new SimpleDateFormat("HH:mm:ss");
    for(int i=0;;i++){
    calendar=Calendar.getInstance();
    calendar.add(calendar.HOUR_OF_DAY, 16);
    calendar.add(calendar.MINUTE, 4);
    date=calendar.getTime();
    str=sdf.format(date);
    if(i>0){
    System.out.println(str+" "+System.currentTimeMillis()%1000);
    }
    try{
    Thread.sleep(1000-System.currentTimeMillis()%1000);
    }catch(InterruptedException e){
    }
    }
    }
    };
    t.start();
    }

    }