9

我有一个奇怪的问题 - 我希望有人可以向我解释发生了什么以及可能的解决方法。我正在用 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。

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

4

3 回答 3

5

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

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

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

于 2011-09-26T23:05:51.130 回答
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)过载。

于 2011-09-26T20:24:22.570 回答
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 等应用程序,它会提高计时器分辨率。因此,如果您在运行模拟器时碰巧正在听一些音乐,您可能会得到很好的时机。

于 2012-03-22T14:31:09.243 回答