Search code examples
javaconcurrencyscheduledexecutorservice

ScheduledExecutorService execute tasks late, with low CPU & RAM usage


I need to create multiple tasks, each of that executes every n seconds. I've decided to use ScheduledExecutorService to schedule task execution. The problem is tasks not executed in time. I thought the reason is not enough processor time, but actual CPU usage is about 4-5 percents.

My schedulers creator:

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");
    }
}

My task:

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);
        }
    }

}

The code at the ideone: https://ideone.com/s3iDif. I've expected time between executions about 2 seconds, but the actual result is 3-4 seconds.

Program output:

...
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

Solution

  • Your code doesn't use the scheduler properly.

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

    This doesn't actually run the code in a new thread. To do that you need to call start(), not run(). Calling run() makes the code execute in the current thread, no different than if you had just written performAction();.

    However, you shouldn't be explicitly creating a new thread at all. You can and should do the work right in MyTask.run().

    Tasks don't need to know about the scheduler or their frequency. Change this code:

    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);
    

    You want the task to repeat, so use the scheduler method that does so. That'll allow the scheduler to adjust the time in between tasks based on how long they take to run.

    Move all of performAction() into MyTask.run(). When you want the task to stop repeating, use the future to cancel it.