Точность синхронизации Java в Windows XP и Windows 7

У меня странная проблема - я надеюсь, что кто-то может объяснить мне, что происходит, и возможный обходной путь. Я реализую ядро ​​Z80 в Java и пытаюсь замедлить его, используя объект java.util.Timer в отдельном потоке.

Базовая настройка состоит в том, что у меня один поток выполняет цикл выполнения 50 раз в секунду. Однако в этом цикле выполнения выполняется много циклов, а затем вызывается wait(). Внешний поток таймера будет вызывать notifyAll() для объекта Z80 каждые 20 мс, имитируя тактовую частоту PAL Sega Master System 3,54 МГц (ish).

Метод, который я описал выше, отлично работает на Windows 7 (пробовал две машины), но я также пробовал две машины с Windows XP, и на обеих из них объект Timer, кажется, переспал примерно на 50% или около того. Это означает, что одна секунда времени эмуляции на машине с Windows XP занимает около 1,5 секунд.

Я попытался использовать 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 этот код выдает время около 999 мс - 1000 мс, что вполне нормально. Запуск такого же jar в Windows XP, однако, дает время около 1562 мс - 1566 мс, и это на двух отдельных машинах XP, которые я тестировал. Все они работают под управлением Java 6, обновление 27.

Я обнаружил, что эта проблема происходит, потому что таймер спит в течение 20 мс (довольно маленькое значение) - если я перевожу все циклы выполнения на одну секунду в цикл wait() - notifyAll(), это дает правильный результат - I' Я уверен, что люди, которые видят, что я пытаюсь сделать (эмулировать Sega Master System со скоростью 50 кадров в секунду), увидят, что это не решение, хотя оно не даст интерактивного времени отклика, пропуская 49 из каждых 50. Как я уже сказал Win7 отлично справляется с этим. Извините, если мой код слишком велик:-(

3 ответа

Решение

Может ли кто-нибудь сказать мне причину этой проблемы и предлагаемый обходной путь?

Проблема, которую вы видите, вероятно, связана с разрешением часов. Некоторые операционные системы (Windows XP и более ранние) печально известны тем, что проспали и медлили с ожиданием / уведомлением / сном (прерывания в общем). Между тем, другие операционные системы (все Linux, которые я видел) превосходно возвращают управление практически в указанный момент.

Обходной путь? Для коротких промежутков времени используйте живое ожидание (занятый цикл). В течение длительного времени спите меньше времени, чем вы действительно хотите, а затем живите, ожидая остатка.

Я бы отказался от 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) перегрузки.

Вы можете установить разрешение таймера в 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, это улучшает разрешение таймера. Так что, если вы услышите какую-то музыку во время работы вашего эмулятора, вы можете получить отличное время.

Другие вопросы по тегам