ScheduledExecutorService выполняет задачи с опозданием, с низким использованием ЦП и ОЗУ

Мне нужно создать несколько задач, каждая из которых выполняется каждые n секунды. Я решил использовать ScheduledExecutorService для планирования выполнения задач. Проблема в том, что задачи не выполняются вовремя. Я думал, что причина в недостаточном количестве процессорного времени, но реальная загрузка процессора составляет около 4-5 процентов.

Создатель моих планировщиков:

class SchedulersCreator {

    private final ScheduledExecutorService scheduler
            = Executors.newScheduledThreadPool(1);

    public SchedulersCreator(int tasksAmount, int repeatCount) {
        for (int taskId = 0; taskId <= tasksAmount; taskId++) {
            // create new task, that executes every 2 seconds
            MyTask task = new MyTask(scheduler, repeatCount, 2, taskId);
            // execute new task
            task.run();
        }
    }

    public static void main(String[] args) {
        System.out.println("Program started");
        // create & start 10 tasks, each of the executes 10 times with period 2 seconds
        SchedulersCreator scheduler = new SchedulersCreator(10, 10);
        System.out.println("All tasks created & started");
    }
}

Мое задание:

class MyTask implements Runnable {

    // number of executions
    private int executesTimesLeft;
    // execution period
    private final int periodSeconds;
    // task id
    private final int id;
    // scheduler
    private ScheduledExecutorService scheduler;
    // field to measure time between executions
    private long lastExecution = 0;

    public MyTask(ScheduledExecutorService scheduler, int executes, int periodSeconds, int id) {
        this.executesTimesLeft = executes;
        this.id = id;
        this.periodSeconds = periodSeconds;
        this.scheduler = scheduler;
    }

    private void performAction() {
        long before = System.currentTimeMillis();
        long time = (before - lastExecution) % 1_000_000;
        lastExecution = before;

// Simulates useful calculations
        try {
            Thread.sleep(1000);
        } catch (InterruptedException ex) {
        }

        long after = System.currentTimeMillis();
        if (id % 100_000 == 0) {
            long duration = after - before;
            System.out.println("Time since prev execution:\t" + time + "\t"
                    + "Task " + id + ": "
                    + executesTimesLeft + " executions lefts; "
                    + "current duration\t" + duration);
        }

    }

    @Override
    public void run() {
        // perform useful calculation in another thread
        new Thread(() -> performAction()).run();

        executesTimesLeft--;
        if (executesTimesLeft > 0) { // schedule next task execution
            scheduler.schedule(this, periodSeconds, SECONDS);
        }
    }

}

Код на идеоне: https://ideone.com/s3iDif. Я ожидал время между выполнениями около 2 секунд, но фактический результат составляет 3-4 секунды.

Вывод программы:

...
Time since prev execution:  3028    Task 0: 2 executions lefts; current duration    1000
Time since prev execution:  4001    Task 0: 1 executions lefts; current duration    1001

person Wsl_F    schedule 14.01.2018    source источник


Ответы (1)


Ваш код не использует планировщик должным образом.

// perform useful calculation in another thread
new Thread(() -> performAction()).run();

На самом деле это не запускает код в новом потоке. Для этого вам нужно позвонить start(), а не run(). Вызов run() заставляет код выполняться в текущем потоке, как если бы вы только что написали performAction();.

Однако вы вообще не должны явно создавать новый поток. Вы можете и должны делать работу прямо в MyTask.run().

Заданиям не нужно знать о планировщике или их частоте. Измените этот код:

MyTask task = new MyTask(scheduler, repeatCount, 2, taskId);
// execute new task
task.run();

to:

MyTask task = new MyTask(repeatCount, taskId);
Future<?> future = scheduler.scheduleAtFixedRate(task, 0, 2, SECONDS);

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

Переместите все performAction() в MyTask.run(). Если вы хотите, чтобы задача перестала повторяться, используйте future, чтобы отменить ее.

person John Kugelman    schedule 14.01.2018