【问题标题】:Java timing accuracy on Windows XP vs. Windows 7Windows XP 与 Windows 7 上的 Java 计时精度
【发布时间】:2011-11-25 09:32:59
【问题描述】:

我有一个奇怪的问题 - 我希望有人可以向我解释正在发生的事情以及可能的解决方法。我正在用 Java 实现 Z80 内核,并试图通过在单独的线程中使用 java.util.Timer 对象来减慢它的速度。

基本设置是我有一个线程运行一个执行循环,每秒 50 次。在这个执行循环中,无论执行多少个循环,然后调用 wait()。外部 Timer 线程将每 20 毫秒调用一次 Z80 对象上的 notifyAll(),模拟 3.54 MHz (ish) 的 PAL Sega Master System 时钟频率。

我上面描述的方法在 Windows 7 上完美运行(尝试了两台机器),但我也尝试了两台 Windows XP 机器,在这两台机器上,Timer 对象似乎睡过头了大约 50% 左右。这意味着在 Windows XP 机器上,一秒的仿真时间实际上大约需要 1.5 秒左右。

我尝试过使用 Thread.sleep() 代替 Timer 对象,但这具有完全相同的效果。我意识到大多数操作系统中的时间粒度并不优于 1 毫秒,但我可以忍受 999 毫秒或 1001 毫秒而不是 1000 毫秒。我无法忍受的是 1562 毫秒 - 我只是不明白为什么我的方法在较新版本的 Windows 上运行良好,但在较旧版本的 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 上,此代码产生大约 999 毫秒 - 1000 毫秒的时间,这完全没问题。然而,在 Windows XP 上运行相同的 jar 会产生大约 1562 毫秒 - 1566 毫秒的时间,这是在我测试过的两台单独的 XP 机器上。它们都在运行 Java 6 update 27。

我发现这个问题正在发生,因为 Timer 休眠了 20 毫秒(相当小的值) - 如果我将所有执行循环一秒钟放入等待 wait() - notifyAll() 循环,这会产生正确的结果- 我确信看到我正在尝试做的事情(以 50fps 模拟世嘉主系统)的人会明白这不是一个解决方案 - 它不会给出交互式响应时间,每 50 个跳过 49 个。正如我所说,Win7 可以很好地解决这个问题。对不起,如果我的代码太大:-(

【问题讨论】:

  • 有趣的问题。您是否有可能提供表现出这种行为的自包含代码 sn-p?
  • 如果你愿意,我可以给你Timer的源代码吗?我会给你很多,但 Z80 核心正在形成相当大的 ;-)
  • @aix "自包含代码 sn-p" 自包含代码可能很短,但它不能(根据定义)是 snippet。我建议发布SSCCE
  • 多么甜蜜的项目! Z-80 是有史以来最伟大的机器之一。 i8085一问世,就真的让他大吃一惊。我最喜欢的 CPU,没有例外。如果您遇到问题,请给我发电子邮件(请参阅个人资料);我很乐意提供帮助。
  • @AndrewThompson:我认为 SO 缺少的是 Nitpicker 徽章。我们都知道我的意思。 ;-)

标签: java multithreading timing z80


【解决方案1】:

谁能告诉我这个问题的原因和建议的解决方法?

您看到的问题可能与clock resolution 有关。某些操作系统(Windows XP 和更早版本)因过度睡眠和等待/通知/睡眠(通常是中断)速度慢而臭名昭著。同时,其他操作系统(我见过的每一个 Linux)都非常擅长在几乎指定的时刻返回控制权。

解决方法?对于较短的持续时间,请使用实时等待(忙碌循环)。长时间睡眠,比你真正想要的时间少,然后继续等待剩余的时间。

【讨论】:

  • 就像添加一样,在使用循环时,使用System.nanoTime() 而不是System.currentTimeMillis(),因为 currentTimeMillis() 与 Thread.sleep() 具有相同的粒度。 Link 进行更多讨论。
  • 有没有办法在 XP 上将时钟分辨率设置为 1 毫秒?我的印象是——但显然我没有做到这一点,我可能是错的;-)
  • 对不起,没有。这是操作系统的事情。 Java 程序可能无意中成为特定于平台的隐藏方式之一。
  • 好的,谢谢你的回答蒂姆,以及其他所有人:-) 我想我会使用我在更现代的操作系统上已经拥有的计时器解决方案,因为它工作得很好 - 我会编写代码当时间超过合理数量时退回到忙等待循环:-) 到达那里 - 我现在已经实现了大约 14 个操作码......只有几百个;-)
  • @PhilPotter1987,在我上班的路上,我突然想到使用ScheduledExecutorService 可能会得到不同的结果,无论如何这都是Java 5 的Timer 替代品。另一种可能性是Condition#awaitNanos。两者都没有解决时钟分辨率,但实现可能会解释它(尽管我对此表示怀疑)。您可能还想 wiki spurious wakeup,JVM 实现可能使用的一些线程库可能会导致线程在没有信号的情况下唤醒。不过,我还没有听说有人遇到过这种情况。
【解决方案2】:

我会放弃 TimerTask 并使用繁忙的循环:

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

两毫秒的延迟让主机操作系统有足够的时间来处理其他事情(而且你很可能在多核上)。剩下的程序代码要简单得多。

如果硬编码的 2 毫秒过于生硬,您可以计算所需的睡眠时间并使用 Thread.sleep(long, int) 过载。

【讨论】:

  • 感谢这个 Barend - 我没有考虑过这个。我试过了,它离我需要的时间范围更近了——但仍然不够。即使在计算所需的睡眠时间并使用 Thread.sleep(long, int) 时,我的平均睡眠时间仍然约为 90 毫秒。正如我所说,我不介意延迟一两毫秒,但这太多了。不过感谢您的建议 - 这是一个很好的建议,当我下班回家并在 Windows 7 上尝试时,我毫不怀疑它会运行良好 - 但它仍然无法回答为什么会存在这个问题。
【解决方案3】:

可以在 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 细节来获得良好的时序分辨率。最后,我使用了一个睡眠线程,如果每 20 毫秒超时超过 1 毫秒,则代码会退回到繁忙的等待循环。现在似乎在 XP 上运行良好。
  • Phil - 这听起来像是一个很好的多平台解决方案,无需为每个平台编写特殊情况代码。不错
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-02-20
  • 1970-01-01
  • 2012-11-06
  • 2011-03-03
  • 2012-06-17
  • 2012-07-17
  • 2011-05-28
相关资源
最近更新 更多