Ошибка Java во сне() при изменении времени работы ОС: любое обходное решение?

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

Приложение, которое я разрабатываю, предназначено для работы 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 более простой реализацией. Это решает проблему для всех машин, кроме моих (я просто надеюсь, что моя машина является исключением больше, чем правило).

Ответы

Ответ 1

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

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

Изменить:

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

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

Ответ 2

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

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

Ответ 3

Пожалуйста, проверьте последнюю версию 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.

Ответ 4

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

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

Ответ 5

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