2016-03-28 40 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