Guy Yafe
Guy Yafe

Reputation: 1021

Java: Get Method on a Future from scheduleAtFixedRate

Consider the following code:

public static void main(String ... args) throws InterruptedException {
  ScheduledExecutorService threadsPool = Executors.newSingleThreadScheduledExecutor();
  Future<?> f = threadsPool.scheduleAtFixedRate(() -> {
    try {
      Thread.sleep(1);
    } catch (Exception e) {
      e.printStackTrace();
    }
  }, 0, 2, TimeUnit.SECONDS);

  Thread.sleep(5000);
  threadsPool.shutdown();
  System.out.println(threadsPool.awaitTermination(1, TimeUnit.SECONDS));
  try {
    f.get();
  } catch (Exception e) {
    e.printStackTrace();
  }
}

Note the sleeping time of the evil thread: 1ms. This guarantees that the shutdown will be while the threads pool is waiting for the next iteration and the evil thread is not running.
This results in a a CancellationException on the get() method: If I understand correctly, ScheduledExecutorService cancels any pending task when calling shutdown(), so this behavior makes sense.

Next I have changed the sleeping time of evil thread from 1 to 1999. This guarantees that the shutdown will be during the sleeping of the evil thread.
This results in waiting forever on the get() method.

My next question is why is this behavior happening? Calling shutdown will gracefully shutdown the service. Indeed, the evil thread finishes the iteration and doesn't start again.
But why doesn't the get() method return? Am I misunderstanding the get() method on ScheduledFuture?
I thought that as soon as the evil thread finishes, and the pool is shutdown, the get() method should return null.

Upvotes: 2

Views: 1757

Answers (1)

Spyros K
Spyros K

Reputation: 2605

If the future did not complete then it is possible that the future.cancel() method was invoked by the shutdown method (or the executor somehow cancelled the future). This is the expected behavior of requesting the executor to shutdown, since it does not wait for tasks to complete.

If the future has been cancelled before finishing, throwing the CancellationException is the expected behavior. Otherwise it would wait for ether for the task to return.

In this case, since you use ScheduledExecutorService you can use the ScheduledFuture instead of Future in this case to get more information. https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ScheduledFuture.html This will allow you to access the getDelay method provided by the Delayed interface. https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Delayed.html

If you also check the source code of the method scheduleAtFixedRate you will see that it actually creates a ScheduledFutureTask. See code below. (Copy from source.)

/**
     * @throws RejectedExecutionException {@inheritDoc}
     * @throws NullPointerException       {@inheritDoc}
     * @throws IllegalArgumentException   {@inheritDoc}
     */
    public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                                  long initialDelay,
                                                  long period,
                                                  TimeUnit unit) {
        if (command == null || unit == null)
            throw new NullPointerException();
        if (period <= 0)
            throw new IllegalArgumentException();
        ScheduledFutureTask<Void> sft =
            new ScheduledFutureTask<Void>(command,
                                          null,
                                          triggerTime(initialDelay, unit),
                                          unit.toNanos(period));
        RunnableScheduledFuture<Void> t = decorateTask(command, sft);
        sft.outerTask = t;
        delayedExecute(t);
        return t;
    }

The ScheduledFutureTask's run method automatically reexecutes it as soon as it is finished. (see last line of source code).

 /**
         * Overrides FutureTask version so as to reset/requeue if periodic.
         */
        public void run() {
            boolean periodic = isPeriodic();
            if (!canRunInCurrentRunState(periodic))
                cancel(false);
            else if (!periodic)
                ScheduledFutureTask.super.run();
            else if (ScheduledFutureTask.super.runAndReset()) {
                setNextRunTime();
                reExecutePeriodic(outerTask);
            }
        }

The outerTask is this so it is actually the ScheduledFutureTask itself. So it is never complete. For this reason you can never actually get the result.

Upvotes: 1

Related Questions