CompletableFuture thenCompose与thenComposeAsync线程行为不符认知的疑问
thenCompose use a new thread while thenComposeAsync reuses threads in my test? Let’s clear up the core misunderstanding first: your initial assumption about thenCompose and thenComposeAsync focusing on "same thread vs new thread" is off the mark. The real difference between these two methods lies in where the transformation function (the lambda that returns the next CompletableFuture) is executed, not directly in whether the subsequent task uses a new thread.
Breaking down the behavior of thenCompose
When you use thenCompose:
- Once the upstream CompletableFuture completes, the transformation lambda (the code that creates your second
supplyAsynctask) runs synchronously on the thread that finished the upstream task (e.g.,pool-1-thread-2in your test after stage 1 completes). - However, the second
supplyAsync(..., executorService)you create inside that lambda submits its task to yournewCachedThreadPool. This thread pool’s strategy is to create new threads if no idle threads are available. In your test, when the first stage 1 completes, the original thread (pool-1-thread-2) might still be occupied executing thethenComposelambda itself, or the thread pool scheduler might choose to spin up a new thread for the stage 2 task instead of waiting for an idle one—hence thepool-1-thread-4you see.
Breaking down the behavior of thenComposeAsync
For thenComposeAsync:
- The key difference is that the transformation lambda is submitted to an executor (by default,
ForkJoinPool.commonPool()) instead of running on the upstream completion thread. - In your test, since you didn’t specify a custom executor for
thenComposeAsync, the lambda runs in the common pool. By the time this lambda submits the stage 2supplyAsynctask to yournewCachedThreadPool, the original stage 1 threads (likepool-2-thread-1) have already finished their work and are idle. The cached thread pool then reuses these idle threads for the stage 2 tasks, which is why you see stage 2 running on the same threads as stage 1.
Reconciliation with Java 8 in Action
The example in Java 8 in Action likely aimed to demonstrate that thenCompose directly chains asynchronous tasks (triggering the next task submission immediately after the first completes) rather than emphasizing thread reuse. The thread reuse you might have inferred from the book is a side effect of the executor’s thread pooling strategy, not a guaranteed behavior of thenCompose itself. The book’s code also uses a custom executor for the second supplyAsync, so thread allocation is still controlled by that executor.
Key Takeaways
- Stop thinking in terms of "same thread vs new thread" for these methods. Focus on where the transformation function runs:
thenCompose: Transformation runs on the upstream completion thread (synchronously relative to that thread).thenComposeAsync: Transformation runs asynchronously on a specified executor (or the common pool).
- The thread used for the actual downstream task (your stage 2) is entirely determined by the executor you pass to
supplyAsync, not bythenCompose/thenComposeAsync. The thread reuse or creation you saw is a product ofnewCachedThreadPool’s dynamic thread management.
内容的提问来源于stack exchange,提问作者Hearen

