Java CompletableFuture传递Executor的方式差异及线程执行分析
CompletableFuture 线程切换逻辑详解(4种组合场景分析)
先明确核心规则:
- 不带
Async后缀的方法(比如thenCompose):直接在前一个Future完成的线程上执行,不会启动新线程; - 带
Async后缀的方法(比如thenComposeAsync、supplyAsync):如果指定了Executor,就用该线程池的线程;没指定则默认用ForkJoinPool.commonPool()。
以下是4种常见组合的代码、日志及逻辑分析:
示例1:thenCompose + 无指定Executor的supplyAsync
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> { System.out.println("supplyAsync 执行线程: " + Thread.currentThread().getName()); return "第一步结果"; }) .thenCompose(result -> { System.out.println("thenCompose 执行线程: " + Thread.currentThread().getName()); return CompletableFuture.supplyAsync(() -> { System.out.println("内部supplyAsync 执行线程: " + Thread.currentThread().getName()); return result + " + 第二步结果"; }); }); future.join();
日志示例:
supplyAsync 执行线程: ForkJoinPool.commonPool-worker-1 thenCompose 执行线程: ForkJoinPool.commonPool-worker-1 内部supplyAsync 执行线程: ForkJoinPool.commonPool-worker-2
逻辑分析:
- 外层
supplyAsync用默认公共线程池的worker-1执行; thenCompose是同步方法,直接复用worker-1线程执行后续逻辑;- 内部
supplyAsync是异步方法,从公共线程池拿另一个空闲线程worker-2执行。
示例2:指定自定义Executor的thenComposeAsync + supplyAsync
Executor customExecutor = Executors.newFixedThreadPool(2); CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> { System.out.println("supplyAsync 执行线程: " + Thread.currentThread().getName()); return "第一步结果"; }) .thenComposeAsync(result -> { System.out.println("thenComposeAsync 执行线程: " + Thread.currentThread().getName()); return CompletableFuture.supplyAsync(() -> { System.out.println("内部supplyAsync 执行线程: " + Thread.currentThread().getName()); return result + " + 第二步结果"; }, customExecutor); }, customExecutor); future.join();
日志示例:
supplyAsync 执行线程: ForkJoinPool.commonPool-worker-1 thenComposeAsync 执行线程: pool-1-thread-1 内部supplyAsync 执行线程: pool-1-thread-2
逻辑分析:
- 外层
supplyAsync用默认公共线程池的worker-1; thenComposeAsync指定了自定义线程池,直接切换到pool-1的thread-1执行;- 内部
supplyAsync同样指定自定义线程池,用pool-1的thread-2执行(线程池有2个可用线程)。
示例3:指定Executor的supplyAsync + thenCompose
Executor customExecutor = Executors.newSingleThreadExecutor(); CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> { System.out.println("supplyAsync 执行线程: " + Thread.currentThread().getName()); return "第一步结果"; }, customExecutor) .thenCompose(result -> { System.out.println("thenCompose 执行线程: " + Thread.currentThread().getName()); return CompletableFuture.completedFuture(result + " + 第二步结果"); }); future.join();
日志示例:
supplyAsync 执行线程: pool-1-thread-1 thenCompose 执行线程: pool-1-thread-1
逻辑分析:
- 外层
supplyAsync用自定义线程池的thread-1; thenCompose是同步方法,继续在thread-1上执行,完全没有线程切换;completedFuture是立即完成的,整个后续逻辑都复用同一个线程。
示例4:无指定Executor的thenComposeAsync + 指定Executor的supplyAsync
Executor customExecutor = Executors.newFixedThreadPool(1); CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> { System.out.println("supplyAsync 执行线程: " + Thread.currentThread().getName()); return "第一步结果"; }) .thenComposeAsync(result -> { System.out.println("thenComposeAsync 执行线程: " + Thread.currentThread().getName()); return CompletableFuture.supplyAsync(() -> { System.out.println("内部supplyAsync 执行线程: " + Thread.currentThread().getName()); return result + " + 第二步结果"; }, customExecutor); }); future.join();
日志示例:
supplyAsync 执行线程: ForkJoinPool.commonPool-worker-1 thenComposeAsync 执行线程: ForkJoinPool.commonPool-worker-2 内部supplyAsync 执行线程: pool-1-thread-1
逻辑分析:
- 外层
supplyAsync用默认公共线程池的worker-1; thenComposeAsync没指定Executor,用公共线程池的另一个线程worker-2;- 内部
supplyAsync指定了自定义线程池,切换到pool-1的thread-1执行。
核心差异总结
- thenCompose(同步):复用前一阶段的线程,无切换开销,适合轻量的后续处理;
- thenComposeAsync(异步):
- 无指定Executor:走默认公共线程池;
- 指定Executor:强制切换到目标线程池,适合需要隔离资源的场景(比如IO任务单独用线程池);
- supplyAsync:不管前序线程是什么,指定Executor就用目标池,否则用默认公共池,是主动启动异步任务的入口。
内容的提问来源于stack exchange,提问作者Student
相关产品推荐
相关产品推荐

