位置:首页 > Java > CompletableFuture实现线程池管理的方法与实践

CompletableFuture实现线程池管理的方法与实践

时间:2026-08-15  |  作者:怪兽小助手  |  阅读:0

Callable接口做多线程开发时,通常会借助FutureTask来管理并拿到线程执行结果。

但问题也恰恰出在这里:它在获取结果时,基本还是依赖阻塞或轮询这两种方式。这不仅有点背离多线程编程追求的高效初衷,还会额外消耗不少资源。

也正因为如此,JDK 8专门针对FutureTask这类短板做了增强,推出了一个非常实用的函数式异步编程辅助类CompletableFuture

这个类同时实现了Future接口CompletionStage接口(Ja va 8中新增的一个线程任务完成结果接口)。它在Future能力之上做了大幅扩展,也让异步编程这件事变得简单了很多。

CompletableFuture对象的创建方式

在使用CompletableFuture类在进行线程管理时,通常会使用四个静态方法来为一段异步执行的代码创建CompletableFuture对象,具体如表1所示。

表1 CompletableFuture对象创建的四个静态方法

方法声明 功能描述
static CompletableFuture runAsync(Runnable runnable) 以Runnable函数式接口类型为参数,并使用ForkJoinPool.commonPool()作为它的线程池执行异步代码获取CompletableFuture计算结果为空的对象
static CompletableFuture runAsync(Runnable runnable, Executor executor) 以Runnable函数式接口类型为参数,并传入指定的线程池执行器executor来获取CompletableFuture计算结果为空的对象
static CompletableFuture supplyAsync(Supplier supplier) 以Supplier函数式接口类型为参数,并使用ForkJoinPool.commonPool()作为它的线程池执行异步代码获取CompletableFuture计算结果非空的对象
static CompletableFuture supplyAsync(Supplier supplier, Executor executor) 以Supplier函数式接口类型为参数,并传入指定的线程池执行器executor来获取CompletableFuture计算结果非空的对象

两个核心区别

  • runAsync()supplyAsync()的本质区别,在于获取的CompletableFuture对象是否带有计算结果。这个区别类似于Runnable接口和Callable接口的区别。
  • 带有Executor参数的方法,用于传入指定的线程池执行器来进行多线程管理;未带有Executor参数的方法,会使用默认的ForkJoinPool.commonPool()作为它的线程池进行多线程管理。

案例:通过CompletableFuture实现线程池管理

了解了创建CompletableFuture对象的常用方法后,接下来通过一个案例来演示如何通过CompletableFuture来实现线程池管理,如文件1所示。

文件1 Example19.ja va

 1    import ja va.util.concurrent.*;
 2    public class Example19 {
 3        public static void main(String[] args) throws InterruptedException,
 4                                                      ExecutionException {
 5            // 1、创建第一个线程,执行1到5相加运算
 6            CompletableFuture completableFuture1 = 
 7                                    CompletableFuture.supplyAsync(() -> {
 8                int sum = 0, i = 0;
 9                while (i++ < 5) {
 10                    sum += i;
 11                    // 显示线程任务执行过程
 12                    System.out.println(Thread.currentThread().getName() 
 13                                         + "线程任务正在执行...i:" + i);
 14                }
 15                return sum;
 16            });
 17            // 创建第二个线程,执行6到10相加运算
 18            CompletableFuture completableFuture2 = 
 19                                    CompletableFuture.supplyAsync(() -> {
 20                int sum = 0, j = 5;
 21                while (j++ < 10) {
 22                    sum += j;
 23                    System.out.println(Thread.currentThread().getName() 
 24                                         + "线程任务正在执行...j:" + j);
 25                }
 26                return sum;
 27            });
 28            // 2、将两个线程执行结果进行获取整合
 29            CompletableFuture completableFuture3 = 
 30                        completableFuture1.thenCombine(completableFuture2,
 31                    (result1, result2) -> result1 + result2);
 32             System.out.println("1到10相加的结果为:"
 33                                 + completableFuture3.get());
 34        }
 35    }

运行结果如图1所示。

CompletableFuture实现线程池管理的方法与实践_wishdown.com

图1 运行结果

运行结果分析

从图1可以看出,在执行1到10相加运算时,将启动的两个线程worker-1和worker-2默认提交到了ForkJoinPool.commonPool()线程池进行管理。

与此同时,还可以整合获取这两个线程任务的执行结果。

小提示

运行文件1中的这段代码时,确实有可能出现这样一种情况:线程池里只有一个线程,比如worker-1,把1到5和6到10这两段求和任务都包办了。

原因并不复杂,关键就在于这个案例使用的是线程池来统一调度任务。

1到5求和、6到10求和这两个子任务在执行时,线程池中的空闲线程都会去竞争执行机会。于是,就可能出现这样的结果:worker-1刚跑完前一个任务,紧接着又抢到了下一个任务的执行权。

这样一来,1到10的求和过程就可能始终落在同一个线程的执行控制之下。

免责声明:文中图文均来自网络,如有侵权请联系删除,心愿游戏发布此文仅为传递信息,不代表心愿游戏认同其观点或证实其描述。

相关文章

更多

精选合集

更多

大家都在玩

热门话题

大家都在看

更多