Windows XP和Windows 7上Java的时间精度比较

9
我有一个奇怪的问题 - 希望有人能向我解释发生了什么,并提供一种可能的解决方法。我正在Java中实现Z80核心,并尝试通过在单独的线程中使用java.util.Timer对象来减速。
基本设置是有一个线程每秒运行50次执行循环。在这个执行循环中,无论执行多少个周期,都会调用wait()。外部Timer线程将每20ms在Z80对象上调用notifyAll(),模拟PAL世嘉主系统时钟频率为3.54 MHz(左右)。
我上面描述的方法在Windows 7上完美运行(我尝试过两台机器),但我也尝试过两台Windows XP机器,在这两台机器上,Timer对象似乎会多睡觉约50%左右。这意味着一秒钟的仿真时间实际上需要大约1.5秒钟在Windows XP计算机上。
我已经尝试使用Thread.sleep()而不是Timer对象,但是效果完全相同。我知道大多数操作系统中的时间粒度都不超过1毫秒,但是我可以接受999毫秒或1001毫秒而不是1000毫秒。我不能忍受的是1562毫秒 - 我只是不明白我的方法为什么在较新版本的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。
我发现这个问题发生是因为计时器休眠了20ms(相当小的值)-如果我把所有执行循环放入等待wait() - notifyAll()周期中的一秒钟,这会产生正确的结果-我相信那些看到我试图做什么的人(模拟50fps的塞加主系统)将会看到这不是一个解决方案-它不会给出交互响应时间,跳过每50个中的49个。正如我所说,Win7可以很好地处理这个问题。如果我的代码太大,对不起 :-(

1
@aix "自包含的代码片段" 自包含的代码可能很短,但是它(根据定义)不能是“片段”。我建议发布一个SSCCE - Andrew Thompson
3
我认为Stack Overflow缺少的是一个“吹毛求疵者”徽章。我们都知道我想表达什么。;-) - NPE
@aix "@AndrewThompson: 我认为SO缺少的是一个挑剔者徽章。" 我的名字有空格。..那我现在能得到两个徽章吗? :P - Andrew Thompson
不同操作系统的行为变化可能是由于计时器源的改变。操作系统有各种硬件计时器可用,其中一些可能比其他计时器更准确(基于HPET的计时器是相对较新的开发,早期计时源依赖于较旧的中断控制器)。 - ritesh
可能相关:https://dev59.com/kHA75IYBdhLWcg3weY_u - finnw
显示剩余6条评论
3个回答

5
请问有人能告诉我这个问题的原因和建议的解决方法吗?
你看到的问题可能与时钟分辨率有关。一些操作系统(如Windows XP及以前版本)以过度睡眠和等待/通知/休眠(中断等)缓慢著称。而其他操作系统(我见过的每个Linux)则非常擅长在几乎指定的时刻返回控制权。
解决方法?对于短时间,使用实时等待(忙等待)。对于长时间,休眠比您实际需要的时间少,然后实时等待余下的时间。

1
只是想补充一点,当使用循环时,请使用System.nanoTime()而不是System.currentTimeMillis(),因为currentTimeMillis()的粒度与Thread.sleep()相同。链接提供了额外的讨论。 - prunge
有没有办法在XP上将时钟分辨率设置为1毫秒?我原本以为有这个功能,但显然我还没有成功,可能是我错了;-) - PhilPotter1987
抱歉,不行。这是一个操作系统的问题。这是Java程序无意中变得平台特定的那些隐藏方式之一。 - Tim Bender
1
好的,无论如何感谢你的回答Tim,还有其他人 :-) 我想我会继续使用我已经在更现代的操作系统上拥有的计时器解决方案,因为它完美地工作 - 当时间偏差超过合理范围时,我将编写代码以回退到繁忙等待循环 :-) 我们正在努力实现 - 我已经实现了大约14个操作码...只剩下几百个了 ;-) - PhilPotter1987
@PhilPotter1987,我在上班的路上想到,你可能会得到不同的结果,如果使用ScheduledExecutorService,这是Java 5替代Timer的方式。另一个可能性是Condition#awaitNanos。两者都没有解决时钟分辨率的问题,但实现可能会考虑它(尽管我怀疑)。你还可以查看维基百科上的“虚假唤醒”,一些线程库可能会导致线程在没有信号的情况下被唤醒。虽然我还没有听说过有人遇到这种情况。 - Tim Bender

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)重载。

谢谢你,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 的应用程序,则它会改善计时器分辨率。因此,如果您在运行模拟器时正在听音乐,您可能会获得很好的计时。


谢谢您。我的模拟器专门设计为跨平台使用,无法依赖于Windows特定的功能以获得良好的时间分辨率。最终,我使用了一个睡眠线程,并编写代码以在每20ms时限内,如果时间超出1ms,则回退到繁忙等待循环。现在在XP上看起来效果不错。 - PhilPotter1987
Phil - 听起来这是一个不错的多平台解决方案,而无需为每个平台编写特殊的代码。不错! - dss539

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接