Ошибка Java в sleep () при изменении времени ОС: есть ли обходной путь? - PullRequest
16 голосов
/ 06 мая 2011

Ошибка, которая меня раздражает, такая же, как этот билет . По сути, если вы измените часы ОС на дату в прошлом, все потоки, которые спали в момент изменения, не будут активированы.

Приложение, которое я разрабатываю, должно работать 24 часа в сутки, и мы хотели бы иметь возможность изменять дату ОС, не останавливая ее (например, переключаться с летнего времени на зимнее время). На данный момент происходит то, что когда мы изменяем дату на прошлое, некоторые части приложения просто замирают. Я наблюдал это на нескольких машинах, в Windows XP и Linux 2.6.37, а также с недавней JVM (1.6.0.22).

Я пробовал много спящих примитивов Java, но все они ведут себя одинаково:

  • Thread.sleep (длинный)
  • Thread.sleep (long, int)
  • Object.wait (длинный)
  • Object.wait (long, int)
  • Thread.join (длинный)
  • Thread.join (long, int)
  • LockSupport.parkNanos (длинный)
  • java.util.Timer
  • javax.swing.Timer

Теперь у меня нет идеи обойти эту проблему. Я думаю, что я ничего не могу сделать, чтобы предотвратить замерзание спящих нитей. Но я хотел бы, по крайней мере, предупредить пользователя при обнаружении опасного изменения системных часов.

Я создал поток мониторинга, который обнаруживает такие изменения:

    Thread t = new Thread(new Runnable() {
        @Override
        public void run() {
            long ms1 = System.currentTimeMillis();
            long ms2;
            while(true) {
                ms2 = ms1;
                ms1 = System.currentTimeMillis();
                if (ms1 < ms2) {
                    warnUserOfPotentialFreeze();
                }
                Thread.yield();
            }                    
        }
    });
    t.setName("clock monitor");
    t.setPriority(Thread.MIN_PRIORITY);
    t.setDaemon(true);
    t.start();

Проблема заключается в том, что это приводит к увеличению загрузки ЦП с 2% до 15% в режиме ожидания.

У вас есть идея обойти исходную проблему, или вы можете придумать другой способ контролировать появление замораживания потоков?

Редактировать

Инго предложил не трогать системные часы. Я согласен, что это вообще не нужно. Проблема в том, что мы не контролируем, что наши клиенты делают со своими компьютерами (мы планируем продавать сотни копий).

Хуже того: одна из наших машин демонстрирует эту проблему без какого-либо ручного вмешательства. Я предполагаю, что ОС (Windows XP) регулярно синхронизирует свои часы с часами RTC, и это заставляет часы ОС возвращаться во времени естественным образом.

Эпилог

Я обнаружил, что некоторые утверждения в моем вопросе были неверными. На самом деле есть две отдельные причины, связанные с моей первоначальной проблемой. Теперь я могу точно сказать две вещи:

  1. Только на моем компьютере (archlinux с ядром 2.6.37 с OpenJDK 64 бит 1.6.0_22), Thread.sleep, Object.wait, Thread.join, LockSupport.parkNanos имеют ту же проблему: они просыпаются только когда системные часы достигают «целевого» времени пробуждения. Тем не менее, простой sleep в моей оболочке не представляет проблемы.

  2. На всех машинах, которые я тестировал (включая мою), java.util.Timer и java.swing.Timer имеют одинаковую проблему (они блокируются, пока не достигнуто «целевое» время).

Итак, я заменил все java Timer на более простую реализацию. Это решает проблему для всех машин, кроме моей (я просто надеюсь, что моя машина - исключение, а не правило).

Ответы [ 5 ]

9 голосов
/ 06 мая 2011

Согласно заявке на ошибку, ваши потоки не заморожены, они возобновятся, как только часы догонят то, что было до изменения (так что, если они перенесли его назад через час, ваши потоки возобновят через 1 час) ,

Конечно, это все еще не очень полезно. Основная причина, по-видимому, заключается в том, что Thread.sleep() преобразуется в системный вызов, который переводит поток в спящий режим до определенной временной отметки в будущем, а не на указанную продолжительность. Чтобы обойти это, вам нужно реализовать собственную версию Thread.sleep(), которая использует System.nanoTime() вместо System.currentTimeMillis() или любой другой зависящий от времени API. Как это сделать без использования встроенного Thread.sleep() Я не могу сказать, однако.

Редактировать:

Или, что если вы создадите какое-то внешнее приложение на другом языке (например, C или что-то еще, что вы предпочитаете), которое ничего не делает, кроме как ожидает определенную продолжительность, а затем завершает работу. Затем вместо вызова Thread.sleep () в Java вы можете создать новый экземпляр этого внешнего процесса, а затем вызвать для него waitFor (). Это «спит» потоком Java для всех практических целей, и, пока ваше внешнее приложение способно спать в течение правильного времени, оно будет возобновляться в нужное время, не зависая и не перегружая процессор.

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

3 голосов
/ 06 мая 2011

Как уже говорили другие, вам определенно не нужно менять системные часы.Отметка времени (миллисекунды с начала эпохи) одинакова для всех компьютеров по всему миру, но местное время зависит от вашего местоположения, наблюдения за переходом на летнее время и т. Д.Поэтому проблема связана с настройками локали ОС и времени / даты.

(Тем не менее, я согласен, что если системные часы меняются, JVM должна обнаружить это и обновить или пробудить спящие потоки для решения проблемы.)

2 голосов
/ 04 июня 2014

Пожалуйста, проверьте последнюю версию 1.7.0_60.Это решает описанную проблему, вызванную смещением системного времени в прошлое, по крайней мере, для систем с версией glibc, выпущенной с 2009 года.

Связанная ошибка http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6900441 была исправлена, и поэтому все функции, упомянутые вами(Thread.sleep, Object.wait, Thread.join, LockSupport.parkNanos, java.util.Timer and java.swing.Timer) должен работать как положено.

Я протестировал его с ядром Linux 3.7.10.

0 голосов
/ 08 февраля 2012

Вы не меняете время ОС для настроек DST!Это не более чем изменение часового пояса.Системные часы всегда должны быть по Гринвичу.И время настенных часов, которое вы показываете пользователю, основано на времени с правильным смещением часового пояса.

0 голосов
/ 06 мая 2011

@ Op. Вы реализовали что-то, что выглядит как «занятое ожидание», и это всегда будет потреблять много ресурсов.

Я согласен с другими, я не понимаю, почему вам нужно менять системные часы, когда вы переходите с летнего на зимнее время.

...