Java scheduleAtFixedRate + Thread.sleep

I'm just exploring method scheduleAtFixedRate of class ScheduledExecutorService in Java.

Here is my suspicious code:


ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);

  Runnable command = () -> {
    System.out.println("Yo");
    try {
      Thread.sleep(4000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  };

  scheduledExecutorService.scheduleAtFixedRate(command, 0, 1, TimeUnit.SECONDS);

I expected that every 1 second scheduledExecutorService will try to take new thread from the pool and start it.

API says: "scheduledExecutorService creates and executes a periodic action that becomes enabled first after the given initial delay, and subsequently with the given period. /(unimportant deleted)/ If any execution of this task takes longer than its period, then subsequent executions may start late, but will not concurrently execute."

Result - every new thread starts every 4 seconds.

So, the questions:

  1. What's the catch - Does Thread.sleep() stop all threads or nuance in this behavior - "If any execution of this task takes longer than its period, then subsequent executions may start late, but will not concurrently execute"?

  2. If "will not concurrently execute" is true in this situation - why do we need this pool of several threads if every thread will start after execution of previous thread?

  3. Is there any simple valid example of usage of scheduleAtFixedRate, where one thread starts while previous still executes?


Solution 1:

  1. The answer is in the quote you provided. Executor waits until the task finishes before launching this task again. It prevents concurrent execution of many instances of one task - in most cases this behaviour is needed. In your case Executor starts a task, then waits 1 second of delay, then waits 3 more seconds until current task is done and only then starts this task again (It does not necessarily start new thread, it may start the task in the same thread).

  2. Your code does not use thread pool at all - you can get exactly same result using single thread executor.

  3. If you want to get this behaviour:

    I expected that every 1 second scheduledExecutorService will try to take new thread from the pool and start it.

    Then you may write is like this:

    ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);
    
    Runnable command = () -> {
        System.out.println("Yo");
        try {
            Thread.sleep(4000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    };
    
    Runnable commandRunner = () -> {
        scheduledExecutorService.schedule(command, 0, TimeUnit.SECONDS);
    }
    
    scheduledExecutorService.scheduleAtFixedRate(commandRunner, 0, 1, TimeUnit.SECONDS);
    

    (It's better to create a single-threaded ScheduledExecutorService that runs commandRunner and create a thread pool based ExecutorService that is used by commandRunner to execute command)

Solution 2:

What's the catch - Does Thread.sleep() stop all threads or nuance in this behavior - "If any execution of this task takes longer than its period, then subsequent executions may start late, but will not concurrently execute"?

  • I didn't quite understand what you mean here. But, essentially speaking, in the code that you have shared, Thread.sleep() is just making the thread execution take 4 seconds, which is longer than the set period of 1 second. Thus, subsequent threads will not execute after 1 second, but only after ~4 seconds of execution of the previous thread.

If "will not concurrently execute" is true in this situation - why do we need this pool of several threads if every thread will start after execution of previous thread?

  • You may want to schedule some other type of threads (which do a different job) in the same executor, which may run in parallel to the code which you have shared. Your current code only needs 1 thread in the pool though, since you are scheduling only one job (Runnable).

Is there any simple valid example of usage of scheduleAtFixedRate, where one thread starts while previous still executes?

  • As stated in the documentation, concurrent execution will not happen for the job that you scheduled at fixed rate (with the current code)