Точность запланированного исполнителя Java

Есть особенность, с которой я столкнулся при использовании запланированных исполнителей Java, и мне было интересно, нормально ли то, что я испытал.

Мне нужно запланировать задачи, которые выполняются с предопределенной скоростью 5 секунд. Ожидается, что время от времени выполнение этих задач будет занимать более 5 секунд, но когда время их выполнения становится меньше 5 секунд, резервный список задач должен запускаться в быстрой последовательности, чтобы наверстать упущенное. При выполнении задач важно знать исходное запланированное время выполнения (например, scheduledExecutionTime() в java.util.TimerTask). Наконец, мне нужно отслеживать разницу между запланированным временем и фактическим временем, чтобы определить, когда расписание «дрейфует» и насколько.

До сих пор я реализовывал все это с помощью исполнителей Java, и следующий класс иллюстрирует общую идею:

public class ExecutorTest {
    public static final long PERIOD = 5000;

    public static void main(String[] args) {
        Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(
                new Command(), 0, PERIOD, TimeUnit.MILLISECONDS);
    }

    private static final class Command implements Runnable {
        long timestamp = 0;

        public void run() {
            long now = System.currentTimeMillis();

            if (timestamp == 0) {
                timestamp = now;
            }

            // Drift is the difference between scheduled time and execution time
            long drift = now - timestamp;

            String format = "Ran at %1$tF %<tT,%<tL; drift: %2$dms";
            System.out.println(String.format(format, now, drift));

            timestamp += PERIOD;
        }
    }
}

Выполнение приведенного выше кода показывает, что дрейф (который в идеале должен быть как можно ближе к 0) колеблется на несколько секунд, в результате чего задачи выполняются либо преждевременно, либо с опозданием. Я построил график по результатам работы примерно за 150 минут:

Дрейф исполнителя Java

Итак, мой первый вопрос: нормально ли это? Моя среда состоит из 32-битной Windows XP и обновления Java 1.5 21 (хотя обновление Java 6 22 дает аналогичные результаты).

Второй вопрос заключается в том, есть ли простой способ уменьшить величину дрейфа. Если я использую простую java.util.Timer или даже просто Thread.sleep(), дрейфа не существует.

Наконец, есть ли лучший способ отслеживания запланированного времени выполнения при использовании запланированных исполнителей?


person Sevas    schedule 07.12.2011    source источник


Ответы (2)


Служба запланированного исполнителя использует System.nanoTime, который не так сильно дрейфует, как currentTimeMillis. За исключением случаев, когда вы работаете в системе XP с более чем одним сокетом ЦП. В XP есть ошибка, из-за которой вызов ОС, используемый System.nanoTime(), не согласуется между сокетами, поэтому, когда поток переключает сокет, на котором он работает, вы можете ожидать, что это будет прыгать. (Это не проблема в Vista/7)

В системе Linux с одним сокетом ваша программа сообщает о дрейфе 0-3 мс.

Попробуйте эту программу.

public static void main(String... args) throws Exception {
    long start = System.nanoTime();
    long time = start;
    while(time < start + 3e10) {
        long now = System.nanoTime();
        if (now < time || now > time + 50000) {
            System.out.println(now - time);
            now = System.nanoTime();
        }
        time = now;
    }
}

В системе i7 я вижу около 10 скачков до 2 мс. Если я использую машину, я вижу больше. Я ожидаю, что вы увидите большие отрицательные и положительные тайминги.

person Peter Lawrey    schedule 07.12.2011

Мне кажется нормальным, колеблется в пределах 5000/2. Вместо форматирования + println следует попробовать быстрое протоколирование, чтобы не измерялся дрейф накладных расходов println. Было бы интересно.

person Joop Eggen    schedule 07.12.2011