2011-09-26 67 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

相关问题