2016-03-28 42 views
2

编辑:虽然我确实同意这个问题的关键取决于Thread.sleep()的准确性,但我一直相信Thread.sleep()对睡眠的偏见超过了所要求的时间。为什么线程在睡眠持续时间到期之前恢复?我可以理解操作系统调度程序不能及时回到线程来唤醒它,但为什么它会早点到达那里呢?如果操作系统可以任意提早唤醒它们,睡眠线程的意义何在?具有显着抖动和错误的多线程时序应用程序?

我正在尝试编写一个类,以在我的项目中执行模块化计时。这个想法是让一个类能够测量我感兴趣的任何特定代码的执行时间。我想做这个测量,而不必在原地编写特定的时间代码,并提供一个干净的模块化界面。

这个概念是建立在一个教练身上,每个跑步者都有多个秒表。我可以用不同的秒表ID来调用一个类来创建测量它们各自相对执行时间的线程。此外,还有一个搭接功能可以测量手表时钟的子区间。该实现集中在Stopwatch(coach)类和Watch(runner)类的使用HashMap。

这是我实现:

import java.util.HashMap; 
import java.util.Map; 
import java.util.Map.Entry; 

public class Stopwatch { 
    private static Map<String, Watch> watchMap = new HashMap<>(); 

    public static boolean start(String watchID) { 
     if(!watchMap.containsKey(watchID)) { 
      watchMap.put(watchID, new Watch()); 
      return true; 
     } else { 
      return false; 
     } 
    } 

    public static void stop(String watchID) { 
     if(watchMap.containsKey(watchID)) { 
      watchMap.get(watchID).stop(); 
     } 
    } 

    public static void startLap(String watchID, String lapID) { 
     if(watchMap.containsKey(watchID)) { 
      watchMap.get(watchID).startLap(lapID); 
     } 
    } 

    public static void endLap(String watchID, String lapID) { 
     if(watchMap.containsKey(watchID)) { 
      watchMap.get(watchID).stopLap(lapID); 
     } 
    } 

    public static void stopAndSystemPrint(String watchID) { 
     if(watchMap.containsKey(watchID)) { 
      Watch watch = watchMap.get(watchID); 
      if(watch.isRunning()) { 
       watch.stop(); 
      } 
      Map<String, Long> lapMap = watch.getLapMap(); 

      System.out.println("/****************** " + watchID 
          + " *******************\\"); 
      System.out.println("Watch started at: " + watch.getStartTime() 
          + " nanosec"); 
      for(Entry<String, Long> lap : lapMap.entrySet()) { 
       System.out.println("\t" + lap.getKey() + ": " 
           + ((double)lap.getValue()/1000000.0) 
           + " msec"); 
      } 
      System.out.println("Watch ended at: " + watch.getEndTime() 
          + " nanosec"); 
      System.out.println("Watch total duration: " 
          + (double)(watch.getDuration()/1000000.0) 
          + " msec"); 
      System.out.println("\\****************** " + watchID 
          + " *******************/\n\n"); 
     } 
    } 

    private static class Watch implements Runnable { 

     private Thread timingThread; 
     private long startTime; 
     private long currentTime; 
     private long endTime; 

     private volatile boolean running; 
     private Map<String, Long> lapMap; 

     public Watch() { 
      startTime = System.nanoTime(); 
      lapMap = new HashMap<>(); 

      running = true; 
      timingThread = new Thread(this); 
      timingThread.start(); 
     } 

     @Override 
     public void run() { 
      while(isRunning()) { 
       currentTime = System.nanoTime(); 
       // 0.5 Microsecond resolution 
       try { 
        Thread.sleep(0, 500); 
       } catch (InterruptedException e) { 
        e.printStackTrace(); 
       } 
      } 
     } 

     public void stop() { 
      running = false; 
      endTime = System.nanoTime(); 
     } 

     public void startLap(String lapID) { 
      lapMap.put(lapID, currentTime); 
     } 

     public void stopLap(String lapID) { 
      if(lapMap.containsKey(lapID)) { 
       lapMap.put(lapID, currentTime - lapMap.get(lapID)); 
      } 
     } 

     public Map<String, Long> getLapMap() { 
      return this.lapMap; 
     } 

     public boolean isRunning() { 
      return this.running; 
     } 

     public long getStartTime() { 
      return this.startTime; 
     } 

     public long getEndTime() { 
      return this.endTime; 
     } 

     public long getDuration() { 
      if(isRunning()) { 
       return currentTime - startTime; 
      } else { 
       return endTime - startTime; 
      } 
     } 
    } 
} 

而且,这里是我使用来测试这个实现的代码:

public class StopwatchTest { 

    public static void main(String[] args) throws InterruptedException { 
     String watch1 = "watch1"; 
     Stopwatch.start(watch1); 

     String watch2 = "watch2"; 
     Stopwatch.start(watch2); 

     String watch3 = "watch3"; 
     Stopwatch.start(watch3); 

     String lap1 = "lap1"; 
     Stopwatch.startLap(watch1, lap1); 
     Stopwatch.startLap(watch2, lap1); 

     Thread.sleep(13); 

     Stopwatch.endLap(watch1, lap1); 
     String lap2 = "lap2"; 
     Stopwatch.startLap(watch1, lap2); 

     Thread.sleep(500); 

     Stopwatch.endLap(watch1, lap2); 

     Stopwatch.endLap(watch2, lap1); 

     Stopwatch.stop(watch3); 

     String lap3 = "lap3"; 
     Stopwatch.startLap(watch1, lap3); 

     Thread.sleep(5000); 

     Stopwatch.endLap(watch1, lap3); 

     Stopwatch.stop(watch1); 
     Stopwatch.stop(watch2); 
     Stopwatch.stop(watch3); 

     Stopwatch.stopAndSystemPrint(watch1); 
     Stopwatch.stopAndSystemPrint(watch2); 
     Stopwatch.stopAndSystemPrint(watch3); 
    } 
} 

最后,输出这个测试可以产生:

/****************** watch1 *******************\ 
Watch started at: 45843652013177 nanosec 
    lap1: 12.461469 msec 
    lap2: 498.615724 msec 
    lap3: 4999.242803 msec 
Watch ended at: 45849165709934 nanosec 
Watch total duration: 5513.696757 msec 
\****************** watch1 *******************/ 


/****************** watch2 *******************\ 
Watch started at: 45843652251560 nanosec 
    lap1: 4.5844165436787E7 msec 
Watch ended at: 45849165711920 nanosec 
Watch total duration: 5513.46036 msec 
\****************** watch2 *******************/ 


/****************** watch3 *******************\ 
Watch started at: 45843652306520 nanosec 
Watch ended at: 45849165713576 nanosec 
Watch total duration: 5513.407056 msec 
\****************** watch3 *******************/ 

这段代码有一些有趣的(对我来说,至少)结果。

一,手表是在1毫秒量级的早晚完成的。尽管纳秒时钟有些不准确,但我会认为,我可以获得比1毫秒更好的精度。也许我忘了一些关于有效数字和准确性的事情。

另一个是,在这个测试结果,watch2完成对这一结果其圈:

Watch started at: 45843652251560 nanosec 
    lap1: 4.5844165436787E7 msec 
Watch ended at: 45849165711920 nanosec 

我检查我是操纵在我stopAndSystemPrint法的价值的方式,但是这似乎并不对错误有任何影响。我只能得出结论,我在那里做的数学是可靠的,而且之前的某些事情有时会被打破。有时候我有点担心,因为 - 我认为 - 它告诉我,我可能在Watch课程中对我的线程做错了什么。看来,单圈持续时间正在被抛出,并导致我的开始时间和结束时间之间的某个值。

我不确定这些问题是排他性的,但如果我必须选择一个来解决,那就是抖动。

有人可以做出正面或反面的原因,为什么会有1ms的抖动?

奖励:为什么手表会不时出现圈数不一致?

+0

可能的重复[Thread.sleep有多准确?](http://stackoverflow.com/questions/18736681/how-accurate-is-thread-sleep) – Basilevs

回答

0

手表会因时常发生混乱,因为您正在执行读取currentTime的线程中的计算,该线程与编写currentTime的线程不同。因此,有时读取的值是未初始化的,即零。在涉及watch2的具体情况中,记录了零圈开始时间,因为初始currentTime值不可用于记录圈开始时间的线程。

要解决此问题,请声明currentTimevolatile。您可能还需要延迟或收益以允许watch在开始任何圈之前进行一次更新。

至于抖动,currentTime不易变的事实可能是部分或全部问题,因为启动和停止的调用线程可能正在处理陈旧的数据。此外,Thread.sleep()仅精确到系统时钟准确的程度,在大多数系统中精度不是纳秒。关于后者的更多信息应在评论中可能重复的Basilevs提及。

+0

具体来说,你是说有时代码之间执行'stopwatch.start(“watch2”)''和'Stopwatch.startLap(“watch2”,“lap1”)'比在'start'方法调用的构造函数中的线程启动更快吗? – TorrentialFire

+0

类别。例如,手表线程可能从未安排过任何执行。或者,监视线程可能已经运行,并且将数据写入正在运行的处理器的内存缓存中,但是数据可能未将其写入主内存或运行调用startLap的线程的处理器的缓存中。请记住,当你处理多个线程时,实际上没有同时性的概念。当您在线程之间共享数据时,您确实需要某种类型的同步,例如易失性声明。 –

+0

真棒洞察力。我忘记了在Java中构建代码时忘记硬件是多么容易。当你公开一些“currentTime”的值必须跳转到主应用程序的线程时,我才会明白。 – TorrentialFire