Reputation: 1021
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
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