2011-09-26 64 views
9

我有一個奇怪的問題 - 我希望有人能向我解釋發生了什麼事以及可能的解決方法。我在Java中實現了一個Z80內核,並試圖通過在單獨的線程中使用java.util.Timer對象來減慢速度。Windows XP與Windows 7的Java計時準確性

基本設置是我有一個線程運行一個執行循環,每秒50次。在這個執行循環中,執行多個循環,然後調用wait()。外部定時器線程將每20ms調用Z80對象的notifyAll(),模擬3.54 MHz(ish)的PAL Sega主系統時鐘頻率。

我上面描述的方法在Windows 7上完美工作(嘗試兩臺機器),但我也嘗試過兩臺Windows XP機器,並且在它們兩個上,Timer對象似乎都以大約50%左右的頻率過度睡着。這意味着一秒鐘的模擬時間實際上在Windows XP機器上花費大約1.5秒左右。

我曾嘗試使用Thread.sleep()而不是Timer對象,但這具有完全相同的效果。我意識到大多數操作系統的時間粒度不會超過1毫秒,但我可以忍受999毫秒或1001毫秒,而不是1000毫秒。我無法忍受的是1562ms - 我只是不明白爲什麼我的方法可以在較新版本的Windows上正常工作,但不是舊版本 - 我已經研究了中斷週期等,但似乎沒有已經開發了一種解決方法。

任何人都可以請告訴我這個問題的原因和建議的解決方法?非常感謝。

更新:下面是一個小程序,我建表現出同樣的問題的完整代碼:

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

public class WorkThread extends Thread 
{ 
    private Timer timerThread; 
    private WakeUpTask timerTask; 

    public WorkThread() 
    { 
     timerThread = new Timer(); 
     timerTask = new WakeUpTask(this); 
    } 

    public void run() 
    { 
     timerThread.schedule(timerTask, 0, 20); 
     while (true) 
     { 
     long startTime = System.nanoTime(); 
     for (int i = 0; i < 50; i++) 
     { 
      int a = 1 + 1; 
      goToSleep(); 
     } 
     long timeTaken = (System.nanoTime() - startTime)/1000000; 
     System.out.println("Time taken this loop: " + timeTaken + " milliseconds"); 
     } 
    } 

    synchronized public void goToSleep() 
    { 
     try 
     { 
     wait(); 
     } 
     catch (InterruptedException e) 
     { 
     System.exit(0); 
     } 
    } 

    synchronized public void wakeUp() 
    { 
     notifyAll(); 
    } 

    private class WakeUpTask extends TimerTask 
    { 
     private WorkThread w; 

     public WakeUpTask(WorkThread t) 
     { 
      w = t; 
     } 

     public void run() 
     { 
      w.wakeUp(); 
     } 
    } 
} 

所有主類所做的就是創建和啓動這些工作線程之一。在Windows 7上,此代碼產生大約999ms到1000ms的時間,這非常好。然而,在Windows XP上運行相同的jar會產生大約1562ms到1566ms的時間,這是在我測試過的兩臺獨立的XP機器上。他們都運行Java 6更新27

我覺得這個問題正在發生,因爲定時器沉睡了20毫秒(相當小的值) - 如果我塞子所有單個第二的執行環插入等待觀望() - notifyAll()循環,這會產生正確的結果 - 我敢肯定,看到我想要做什麼的人(以50fps模擬世嘉主系統)會看到這不是一個解決方案 - 但它不會給出交互式響應時間,每50次跳過49次。正如我所說,Win7可以很好地處理這個問題。很抱歉,如果我的代碼太大:-(

+0

有趣的問題。您是否可以提供展示此行爲的獨立代碼片段? – NPE

+0

如果你喜歡,我可以給你定時器的源代碼嗎?我會給你很多東西,但是Z80內核變得相當龐大;-) – PhilPotter1987

+1

@aix *「獨立代碼片段」*自包含代碼可能很短,但它不能(根據定義)是[片段](http://en.wikipedia.org/wiki/Snippet_%28programming%29)。我建議發佈[SSCCE](http://pscode.org/sscce.html)。 –

回答

5

任何人都可以告訴我這個問題的原因和建議的解決方法?

您看到的問題可能與clock resolution有關。一些操作系統(Windows XP及更早版本)因睡眠過多和緩慢等待/通知/休眠(一般中斷)而臭名昭着。與此同時,其他操作系統(我見過的每一個Linux)都非常適合在指定時間內返回控制。

解決方法?對於短時間,請使用實時等待(繁忙循環)。長時間睡眠的時間比你真正想要的要少,然後等待剩下的時間。

+1

就像添加循環一樣,使用[System.nanoTime()](http://download.oracle.com/javase/6/docs/api/java/lang/System.html#nanoTime())因爲currentTimeMillis()與Thread具有相同的粒度,所以它比[System.currentTimeMillis()](http://download.oracle.com/javase/6/docs/api/java/lang/System.html#currentTimeMillis())要短。睡覺()。 [鏈接](http://stackoverflow.com/questions/351565/system-currenttimemillis-vs-system-nanotime)進行額外的討論。 – prunge

+0

在XP上沒有辦法將時鐘分辨率降低到1ms嗎?我的印象是有 - 但顯然,因爲我沒有設法這樣做,我可能是錯的;-) – PhilPotter1987

+0

對不起,沒有。這是一個操作系統的事情。 Java程序可能無意中成爲特定於平臺的隱藏方式之一。 –

2

我會放棄TimerTask,只需使用一個繁忙的循環:

long sleepUntil = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(20); 
while (System.nanoTime() < sleepUntil) { 
    Thread.sleep(2); // catch of InterruptedException left out for brevity 
} 

兩週毫秒的延遲使主機操作系統足夠的時間來對其他的東西工作(而且你很可能是在多核上),其餘的程序代碼要簡單得多

如果硬編碼的兩毫秒是一個鈍的儀器太多,你可以計算所需的睡眠時間和使用Thread.sleep(long, int)過載。

+0

感謝這個Barend - 我沒有考慮過這個。我嘗試了它,它更接近我所需的時間範圍 - 雖然還不夠。即使計算所需的睡眠時間並使用Thread.sleep(long,int),我仍然平均睡眠時間超過90毫秒。正如我所說的,我不介意在一兩毫秒之內就關閉,但這太多了。不過謝謝你的建議 - 這是一個很好的建議,當我下班回家並在Windows 7上試用時,我毫不懷疑它會工作得很好 - 它仍然不能回答爲什麼存在這個問題。 – PhilPotter1987

1

可以在Windows XP上設置計時器分辨率。

http://msdn.microsoft.com/en-us/library/windows/desktop/dd757624%28v=vs.85%29.aspx

由於這是一個系統範圍的設置,你可以使用一個工具來設置分辨率,使您可以驗證這是否是你的問題。

嘗試了這一點,看看它是否幫助:http://www.lucashale.com/timer-resolution/

您可能會看到新版本的Windows更好的時機,因爲在默認情況下,新版本可能有更嚴格的時序。另外,如果您正在運行Windows Media Player等應用程序,則會提高計時器分辨率。所以如果你在運行你的模擬器時碰巧聽到一些音樂,你可能會得到很棒的時間。

+0

感謝你 - 我的模擬器專門設計爲跨平臺,所以不能依靠這樣的Windows具體來獲得良好的時間分辨率。最後,我使用了一個睡眠線程,如果每20ms計時超過1ms,代碼將回退到繁忙的等待循環。似乎XP現在工作得很好。 – PhilPotter1987

+0

菲爾 - 這聽起來像一個很好的多平臺解決方案,無需爲每個平臺編寫特殊的案例代碼。尼斯 – dss539