Ошибка Java в sleep() при изменении времени ОС: есть ли обходной путь?
Ошибка, которая раздражает меня, такая же, как этот билет. По сути, если вы измените часы ОС на дату в прошлом, весь поток, который спал во время изменения, не будет активирован.
Приложение, которое я разрабатываю, предназначено для работы 24/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, и это заставляет часы ОС возвращаться во времени естественным образом.
эпилог
Я узнал, что некоторые утверждения в моем вопросе были неправильными. На самом деле есть две отдельные причины, связанные с моей первоначальной проблемой. Теперь я могу точно сказать две вещи:
Только на моей машине (archlinux с ядром 2.6.37 с OpenJDK 64 бит 1.6.0_22),
Thread.sleep
,Object.wait
,Thread.join
,LockSupport.parkNanos
имеют ту же проблему: они просыпаются только тогда, когда системные часы достигают "целевого" времени пробуждения. Тем не менее, простойsleep
в моей оболочке проблема не проявляется.На всех машинах, которые я тестировал (включая мою),
java.util.Timer
а такжеjava.swing.Timer
имеют ту же проблему (они блокируются, пока не будет достигнуто "целевое" время).
Итак, что я сделал, так это то, что я заменил все Java Timer
более простой реализацией. Это решает проблему для всех машин, кроме моей (я просто надеюсь, что моя машина - исключение, а не правило).
5 ответов
Согласно сообщению об ошибке, ваши потоки не заморожены, они возобновятся, как только часы поймут, где они были до того, как они были изменены (поэтому, если они перенесли его назад через час, ваши потоки возобновят работу через 1 час).
Конечно, это все еще не очень полезно. Коренная причина, кажется, в том, что Thread.sleep()
преобразуется в системный вызов, который переводит поток в спящий режим до определенной временной метки в будущем, а не на указанную продолжительность. Чтобы обойти это, вам нужно реализовать собственную версию Thread.sleep()
который использует System.nanoTime()
вместо System.currentTimeMillis()
или любой другой зависящий от времени API. Как это сделать без использования встроенного Thread.sleep()
Я не могу сказать, однако.
Редактировать:
Или, что если вы создадите какое-то внешнее приложение на другом языке (например, C или что-то еще, что вы предпочитаете), которое ничего не делает, кроме как ожидает заданную продолжительность и затем завершает работу. Затем вместо вызова Thread.sleep() в Java вы можете создать новый экземпляр этого внешнего процесса, а затем вызвать для него waitFor(). Это "спит" потоком Java для всех практических целей, и, пока ваше внешнее приложение может спать в течение правильной продолжительности, оно будет возобновляться в нужное время, не зависая и не перегружая процессор.
Похоже, долгий путь, чтобы решить проблему, но это единственный возможный обходной путь, который я могу придумать. Кроме того, учитывая, что порождение внешнего процесса является относительно дорогой операцией, оно, вероятно, лучше всего работает, если вы спите в течение относительно длительного времени (например, несколько сотен мс или более). Для более коротких промежутков времени он может просто продолжать работать с процессором.
Как уже говорили другие, вам определенно не нужно менять системные часы. Отметка времени (миллисекунды с начала эпохи) одинакова для всех компьютеров по всему миру, но местное время зависит от вашего местоположения, наблюдения за переходом на летнее время и т. Д. Следовательно, проблема заключается в локали ОС и настройках времени и даты.
(Тем не менее я согласен с тем, что если системные часы меняются, JVM должна обнаружить это и обновить или пробудить спящие потоки для решения проблемы.)
Пожалуйста, проверьте последнюю версию 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.
Вы не меняете время ОС для настроек DST! Это не что иное, как изменение часового пояса. Системные часы всегда должны быть по Гринвичу. И время настенных часов, которое вы показываете пользователю, получается из этого с правильным смещением часового пояса.
@Op. Вы внедрили что-то, что выглядит как "занятое ожидание", и это всегда будет потреблять много ресурсов.
Я согласен с остальными, не понимаю, зачем вам менять системные часы при переходе с летнего на зимнее время.