CompletableFuture and async composition question 95 of 180 mid
Why should you pass an explicit executor to the *Async methods?
- To make the code more verbose
- Correct: Because the default is the common ForkJoinPool, sized to cores minus one and shared with parallel streams, so a blocking task starves everything else
- Because the default executor is single threaded
- Because the default is deprecated
supplyAsync(task) and thenApplyAsync(fn) default to ForkJoinPool.commonPool(). It has
availableProcessors() - 1 threads and is shared with every parallel stream in the JVM, including
those inside libraries you did not write. Block on I/O there and you have taken a scarce global
resource out of circulation.
There is a further trap: on a single-core machine, or in a container that reports one CPU, the common
pool has a parallelism of 1, and CompletableFuture refuses a pool that small: every *Async call
without an executor starts a brand-new thread instead. Nothing bounds that, so a burst of calls becomes
a burst of threads.
Pass an executor sized for the work: a bounded pool for blocking calls, and ideally a separate one per downstream dependency so a slow service cannot take the others with it.
Quiz yourself on CompletableFuture and async composition Suggest a correction