16

Is there any way to schedule CompletableFuture in Java? What I wanted to do is to schedule a task to be executed with some delay, and chain it with other operations to be performed asynchronously when it completes. So far I didn't find any way to do this.

For good ol' Futures we have e.g. ScheduledExecutorService, where we can schedule a task to be executed with some delay like this:

ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
Future<String> future = scheduledExecutorService.schedule(() -> "someValue", 10, TimeUnit.SECONDS);

Is there any similar way for CompletableFutures?

Łukasz Gruba
  • 181
  • 1
  • 2
  • 9

2 Answers2

25

If you're using Java 9+ then CompletableFuture#delayedExecutor(long,TimeUnit) may fit your needs:

Returns a new Executor that submits a task to the default executor after the given delay (or no delay if non-positive). Each delay commences upon invocation of the returned executor's execute method.

Executor delayed = CompletableFuture.delayedExecutor(10L, TimeUnit.SECONDS);
CompletableFuture.supplyAsync(() -> "someValue", delayed)
    .thenAccept(System.out::println)
    .join();

There's also an overload where you can specify the Executor to use in place of the "default executor".

Slaw
  • 37,820
  • 8
  • 53
  • 80
12

As said, there is support in Java 9.

But it’s not hard to create a similar feature under Java 8; you already named the necessary elements:

// prefer this constructor with zero core threads for a shared pool,
// to avoid blocking JVM exit
static final ScheduledExecutorService SCHEDULER = new ScheduledThreadPoolExecutor(0);
static Executor delayedExecutor(long delay, TimeUnit unit)
{
  return delayedExecutor(delay, unit, ForkJoinPool.commonPool());
}
static Executor delayedExecutor(long delay, TimeUnit unit, Executor executor)
{
  return r -> SCHEDULER.schedule(() -> executor.execute(r), delay, unit);
}

which can be used similarly to the Java 9 feature:

Executor afterTenSecs = delayedExecutor(10L, TimeUnit.SECONDS);
CompletableFuture<String> future 
  = CompletableFuture.supplyAsync(() -> "someValue", afterTenSecs);

future.thenAccept(System.out::println).join();

Care must be taken to avoid that the shared scheduled executor’s threads prevent the JVM from terminating. The alternative to a zero core pool size is to use daemon threads:

static final ScheduledExecutorService SCHEDULER
  = Executors.newSingleThreadScheduledExecutor(r -> {
    Thread t = new Thread(r);
    t.setDaemon(true);
    return t;
  });
Holger
  • 285,553
  • 42
  • 434
  • 765
  • thanks! it's good to know that there's a built in feature since java 9, but I'm using 8, so the example you shown was exactly what I needed – Łukasz Gruba Nov 15 '19 at 14:39
  • @holger it appears as though `ForkJoinPool.commonPool()` is unused - can you clarify the purpose of this? – Woodz Jan 28 '20 at 09:58
  • 2
    @Woodz a slip on my side, of course, the runnable should be executed on the specified executor after the elipsed time, rather than the `ScheduledThreadPoolExecutor`. – Holger Jan 28 '20 at 10:32
  • @Holger any reason you used ForkJoinPool.commonPool() above? Can we use newCachedThreadPool instead? – Spring Jan 27 '21 at 14:02
  • 1
    @Spring you can use whatever pool you like but because `commonPool()` is the default pool for `CompletableFuture` when no pool has been specified and my methods were intended to be as close as possible to the methods introduced in JDK 9, I used the same default. Mind that there’s still the overload accepting an arbitrary executor. – Holger Jan 27 '21 at 14:06
  • @Holger another question is: how bad it is to just use a Thread.sleep() before I start my completebleFuture, instead of all those stuff above? I tried it, ugly but it worked, but not sure why it can be bad? – Spring Jan 27 '21 at 14:07
  • 2
    @Spring when you submit to a delayed executor you can immediately proceed with something else. When you use `sleep` before submitting, you’re blocking the initiating thread. When you include a `sleep` into the actual action instead, you’ll block a worker thread that could otherwise have worked on a different job. Neither will matter when you do it once and/or have no other work to process, but it can degrade the performance when you do it a lot. Further, when all worker threads are blocked with `sleep`, it may happen that another job supposed to run earlier than the sleeping ones can’t run. – Holger Jan 27 '21 at 14:13
  • @Holger I see thx. I want to use the above example above but I tried and SingleThreadScheduledExecutor didnt work. And I dont want to use ScheduledThreadPoolExecutor as you suggested not to. which worked fine with corepoolSize(4) – Spring Jan 27 '21 at 14:25
  • 1
    @Spring I did not discourage the use of the `ScheduledThreadPoolExecutor`; the `newSingleThreadScheduledExecutor` is just an alternative. But this is only the internally used executor to submit the jobs to the actual executor at the right time, it is not the executor for the actual jobs. The executor for the actual jobs is the `commonPool()` when none has specified or whatever executor you pass to the `delayedExecutor` method (which is outside the scope of this answer; no recommendation has been given). Don’t confuse these two executors. And mind that Java 9+ has this already built in. – Holger Jan 27 '21 at 20:21