CompletableFuture的thenApply为何运行在ForkJoinPool而非调用线程?
CompletableFuture thenApply 运行在ForkJoinPool线程的原因分析
执行CompletableFuture的thenApply方法时,发现其执行过程运行在ForkJoinPool线程而非调用线程(main线程),行为和thenApplyAsync类似,核心原因在于CompletableFuture的回调执行线程由前置任务的完成时机和执行线程共同决定。
测试代码
System.out.println("hello world, CompletableFuturesRightHere"); System.out.println("log1: " + Thread.currentThread().getName()); CompletableFuture<Integer> cf = CompletableFuture.supplyAsync(() -> { System.out.println("log-sA: " + Thread.currentThread().getName()); return 40; }).thenApply( a -> { System.out.println("log-tA: " + Thread.currentThread().getName()); return a + 40; }); System.out.println("log3: " + Thread.currentThread().getName()); System.out.println("cf result: " + cf.get());
输出结果
hello world, CompletableFuturesRightHere log1: main log-sA: ForkJoinPool.commonPool-worker-3 log3: main log-tA: ForkJoinPool.commonPool-worker-3 cf result: 80
原因分析
- 前置任务的执行线程:
supplyAsync()的无参重载默认使用ForkJoinPool.commonPool()执行异步任务,所以你的supplyAsync任务运行在ForkJoinPool的工作线程中。 - 回调的执行时机逻辑:
thenApply是同步注册回调的方法,它不会立刻执行回调逻辑,只是把回调逻辑绑定到前置任务的完成事件上。- 如果前置任务在主线程调用
thenApply()之前就已经完成,回调会在主线程执行;但如果前置任务在主线程调用thenApply()之后才完成,回调会由完成前置任务的线程(也就是执行supplyAsync的ForkJoinPool工作线程)直接触发执行。
在你的测试场景中,supplyAsync的任务耗时极短,主线程在调用thenApply()后立刻执行log3的打印操作,此时前置任务刚好在这段时间内完成,因此回调就由完成它的ForkJoinPool工作线程执行了。
验证示例
场景1:让前置任务延迟完成
修改supplyAsync的任务,增加短暂睡眠,确保它在主线程调用cf.get()之后才完成:
CompletableFuture<Integer> cf = CompletableFuture.supplyAsync(() -> { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("log-sA: " + Thread.currentThread().getName()); return 40; }).thenApply( a -> { System.out.println("log-tA: " + Thread.currentThread().getName()); return a + 40; });
此时输出的log-tA依然会是ForkJoinPool线程,因为前置任务完成时主线程正阻塞在cf.get()上,回调由完成前置任务的线程执行。
场景2:等待前置任务完成后再注册回调
先让主线程等待supplyAsync任务完成,再注册thenApply回调:
CompletableFuture<Integer> supplyCf = CompletableFuture.supplyAsync(() -> { System.out.println("log-sA: " + Thread.currentThread().getName()); return 40; }); // 等待前置任务完成 supplyCf.get(); CompletableFuture<Integer> cf = supplyCf.thenApply(a -> { System.out.println("log-tA: " + Thread.currentThread().getName()); return a + 40; });
此时thenApply的回调会在main线程执行,输出log-tA: main,因为前置任务已经完成,回调在注册它的主线程同步执行。
与thenApplyAsync的区别
thenApply的执行线程由前置任务的完成时机决定,可能是当前注册线程,也可能是前置任务的执行线程。thenApplyAsync则强制将回调提交到指定线程池(默认是ForkJoinPool.commonPool())执行,无论前置任务是否完成,回调都不会在当前注册线程执行。
内容的提问来源于stack exchange,提问作者samshers
相关产品推荐
相关产品推荐

