多线程彻底掌握 CompletableFuture:从入门到项目实战

目录

一、为什么需要 CompletableFuture

在 Java 并发编程中,最早我们通常使用 ThreadRunnableCallableFuture 来执行异步任务。

例如:

java 复制代码
ExecutorService executor = Executors.newFixedThreadPool(5);

Future<String> future = executor.submit(() -> {
    Thread.sleep(1000);
    return "Hello";
});

String result = future.get();

虽然可以实现异步执行,但存在明显缺点:

  • get() 会阻塞当前线程
  • 无法方便地进行任务编排
  • 无法链式调用
  • 多任务组合比较麻烦
  • 异常处理不够优雅

因此 JDK8 引入了 CompletableFuture

它不仅能够表示一个异步任务,还支持:

  • 异步回调
  • 链式调用
  • 多任务编排
  • 异常处理
  • 并发任务组合

在 Spring Boot、微服务、Feign 调用、批量处理等场景中使用非常广泛。


二、创建 CompletableFuture

1、runAsync(无返回值)

执行一个异步任务,但不返回结果。

java 复制代码
CompletableFuture.runAsync(() -> {
    System.out.println("执行异步任务");
});

源码:

java 复制代码
public static CompletableFuture<Void> runAsync(Runnable runnable)

返回值:

java 复制代码
CompletableFuture<Void>

2、supplyAsync(有返回值)

执行异步任务并返回结果。

java 复制代码
CompletableFuture<String> future =
        CompletableFuture.supplyAsync(() -> {
            return "Hello";
        });

获取结果:

java 复制代码
String result = future.join();

输出:

text 复制代码
Hello

3、指定线程池

生产环境不建议直接使用:

java 复制代码
CompletableFuture.runAsync(...)

因为默认使用:

java 复制代码
ForkJoinPool.commonPool()

推荐使用业务线程池:

java 复制代码
ExecutorService executor =
        Executors.newFixedThreadPool(10);

CompletableFuture.runAsync(() -> {
    System.out.println(Thread.currentThread().getName());
}, executor);

三、获取执行结果

1、get()

java 复制代码
String result = future.get();

特点:

  • 阻塞等待
  • 需要处理检查异常
java 复制代码
InterruptedException
ExecutionException

2、join()

java 复制代码
String result = future.join();

特点:

  • 阻塞等待
  • 不需要处理检查异常

项目中使用更频繁。


3、getNow()

立即返回结果。

java 复制代码
String result = future.getNow("默认值");

如果任务未完成:

text 复制代码
返回默认值

四、任务完成后的回调

1、thenAccept

消费结果但不返回值。

java 复制代码
CompletableFuture.supplyAsync(() -> "hello")
        .thenAccept(System.out::println);

输出:

text 复制代码
hello

2、thenRun

不关心前面任务结果。

java 复制代码
CompletableFuture.supplyAsync(() -> "hello")
        .thenRun(() -> {
            System.out.println("执行完成");
        });

3、thenApply

对结果进行转换。

java 复制代码
CompletableFuture<String> future =
        CompletableFuture.supplyAsync(() -> "hello")
                .thenApply(String::toUpperCase);

System.out.println(future.join());

输出:

text 复制代码
HELLO

执行流程:

text 复制代码
hello
 ↓
thenApply
 ↓
HELLO

五、任务编排

1、thenApply

用于结果转换。

java 复制代码
CompletableFuture<Integer> future =
        CompletableFuture.supplyAsync(() -> 10)
                .thenApply(i -> i * 2)
                .thenApply(i -> i + 5);

System.out.println(future.join());

结果:

text 复制代码
25

流程:

text 复制代码
10
 ↓
20
 ↓
25

2、thenCompose

用于依赖任务。

例如:

先查用户,再查订单。

java 复制代码
CompletableFuture<Order> future =
        CompletableFuture.supplyAsync(() -> queryUser(1L))
                .thenCompose(user ->
                        CompletableFuture.supplyAsync(
                                () -> queryOrder(user.getId())
                        ));

流程:

text 复制代码
查询用户
    ↓
获取用户ID
    ↓
查询订单

3、thenApply 与 thenCompose 的区别

错误写法:

java 复制代码
CompletableFuture<CompletableFuture<Order>>
.thenApply(user ->
        CompletableFuture.supplyAsync(
                () -> queryOrder(user.getId())
        ))

得到的是嵌套 Future。


正确写法:

java 复制代码
.thenCompose(...)

结果:

java 复制代码
CompletableFuture<Order>

六、多任务并发执行

这是实际项目最常见的场景。

例如订单详情页面:

需要同时查询:

  • 用户信息
  • 订单信息
  • 发票信息

如果顺序执行:

text 复制代码
1秒 + 1秒 + 1秒 = 3秒

如果并发执行:

text 复制代码
max(1秒,1秒,1秒)=1秒

1、allOf

等待全部任务完成。

java 复制代码
CompletableFuture<User> userFuture =
        CompletableFuture.supplyAsync(() -> queryUser());

CompletableFuture<Order> orderFuture =
        CompletableFuture.supplyAsync(() -> queryOrder());

CompletableFuture<Invoice> invoiceFuture =
        CompletableFuture.supplyAsync(() -> queryInvoice());

CompletableFuture.allOf(
        userFuture,
        orderFuture,
        invoiceFuture
).join();

此时:

text 复制代码
三个任务全部完成
       ↓
继续执行

2、anyOf

任意一个完成即可返回。

java 复制代码
CompletableFuture<Object> future =
        CompletableFuture.anyOf(
                future1,
                future2,
                future3
        );

适用于:

  • 多缓存节点
  • 多数据源
  • 多搜索服务

谁先返回用谁。


七、异常处理

1、exceptionally

发生异常时返回默认值。

java 复制代码
CompletableFuture<Integer> future =
        CompletableFuture.supplyAsync(() -> {
            int i = 1 / 0;
            return 100;
        })
        .exceptionally(ex -> -1);

System.out.println(future.join());

结果:

text 复制代码
-1

2、handle

无论成功还是失败都会执行。

java 复制代码
CompletableFuture<Integer> future =
        CompletableFuture.supplyAsync(() -> 100)
                .handle((result, ex) -> {

                    if (ex != null) {
                        return -1;
                    }

                    return result * 2;
                });

结果:

text 复制代码
200

3、whenComplete

常用于日志记录。

java 复制代码
CompletableFuture.supplyAsync(() -> 100)
        .whenComplete((result, ex) -> {

            System.out.println(result);

            if (ex != null) {
                ex.printStackTrace();
            }
        });

八、重点区别

Future 和 CompletableFuture 的区别

Future CompletableFuture
只能获取结果 支持回调
不支持编排 支持链式编排
不支持组合 支持 allOf
功能简单 功能强大

thenApply 和 thenCompose 区别

thenApply:

text 复制代码
A → B

结果转换。

thenCompose:

text 复制代码
A → CompletableFuture<B>

自动拍平 Future。


allOf 和 anyOf 区别

allOf:

text 复制代码
全部完成继续

anyOf:

text 复制代码
任意完成继续

为什么生产环境要使用自定义线程池

避免所有业务共享:

java 复制代码
ForkJoinPool.commonPool()

导致:

  • 线程耗尽
  • 请求堆积
  • 性能抖动

推荐:

java 复制代码
CompletableFuture.runAsync(
        task,
        customExecutor
);

九、项目实战:批量推送订单到财务系统

实战背景

假设系统需要向财务系统推送 100 个订单。

每个订单调用一次远程接口:

text 复制代码
推送订单1
推送订单2
推送订单3
...
推送订单100

每次推送耗时约 1 秒。

如果串行执行:

text 复制代码
100 × 1秒 = 100秒

用户需要等待很久。

因此我们希望:

text 复制代码
100个订单同时推送

利用线程池并发处理。


第一步:配置线程池

Spring Boot 配置:

java 复制代码
@Configuration
public class AsyncThreadPoolConfig {

    @Bean
    public ThreadPoolTaskExecutor asyncThreadPool() {

        ThreadPoolTaskExecutor executor =
                new ThreadPoolTaskExecutor();

        // 核心线程数
        executor.setCorePoolSize(5);

        // 最大线程数
        executor.setMaxPoolSize(10);

        // 队列容量
        executor.setQueueCapacity(100);

        // 线程名前缀
        executor.setThreadNamePrefix("push-order-");

        // 优雅关闭
        executor.setWaitForTasksToCompleteOnShutdown(true);

        executor.initialize();

        return executor;
    }
}

第二步:编写业务代码

可以直接复制运行观察效果。

java 复制代码
@Service
public class PushOrderService {

    @Autowired
    private ThreadPoolTaskExecutor asyncThreadPool;

    public void pushOrders() {

        // 模拟100个订单
        List<String> orders = IntStream.rangeClosed(1, 100)
                .mapToObj(i -> "ORDER-" + i)
                .collect(Collectors.toList());

        // 用于保存所有异步任务
        List<CompletableFuture<Void>> futures = new ArrayList<>();
        long start = System.currentTimeMillis();
        //通过循环 把100个订单都放进线程池 ,然后放到 futures 里
        for (String orderNo : orders) {
            CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
                try {
                    System.out.println(Thread.currentThread().getName() + " 开始推送:" + orderNo);

                    // 模拟远程调用耗时
                    Thread.sleep(1000);
                    System.out.println(Thread.currentThread().getName() + " 推送成功:" + orderNo);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }, asyncThreadPool);

            futures.add(future);
        }

        //把所有Future合并成一个Future,然后等待全部订单推送完成
        CompletableFuture.allOf(
                futures.toArray(new CompletableFuture[0])
        ).join();

        long end = System.currentTimeMillis();

        System.out.println("总耗时:" + (end - start) + " ms");
    }
}

第三步:理解代码执行流程

假设线程池:

text 复制代码
核心线程数:5

订单:

text 复制代码
ORDER-1
ORDER-2
...
ORDER-100

执行流程:

text 复制代码
线程1 -> ORDER-1
线程2 -> ORDER-2
线程3 -> ORDER-3
线程4 -> ORDER-4
线程5 -> ORDER-5

第一批完成:

text 复制代码
线程1 -> ORDER-6
线程2 -> ORDER-7
...

持续执行。


第四步:allOf 在这里做了什么

核心代码:

java 复制代码
CompletableFuture.allOf(
        futures.toArray(new CompletableFuture[0])
).join();

作用:

text 复制代码
等待所有订单推送完成

流程:

text 复制代码
future1 ────────┐
future2 ────────┤
future3 ────────┤
future4 ────────┤
future5 ────────┘
         ↓
      allOf
         ↓
      join
         ↓
继续执行

如果少了这段代码:

java 复制代码
CompletableFuture.runAsync(...)

提交后方法会立刻结束。

此时:

text 复制代码
订单还没推送完
方法已经返回

而有了:

java 复制代码
allOf(...).join()

系统会等待所有订单推送成功后再继续执行后面的逻辑。


上面的 CompletableFuture 实战在企业项目中也常使用于以下场景:

text 复制代码
批量推送
批量导入
批量同步
批量调用Feign
批量调用第三方接口
相关推荐
用户298698530141 小时前
Java 中的 HTML 解析:从文件读取、URL 抓取到数据提取
java·后端
plainGeekDev1 小时前
ContentProvider → Room + Repository
android·java·kotlin
plainGeekDev1 小时前
SQLite 手动升级 → Room Migration
android·java·kotlin
带刺的坐椅1 小时前
SolonCode(编码智能体)支持鸿蒙 PC
java·web·ai编程·harmonyos·soloncode·鸿蒙 pc
程序员二叉1 小时前
【JVM】类加载全过程&双亲委派机制深度解析
java·jvm·面试
开发者联盟league1 小时前
使用jenkins pipeline将项目打包运行在k8s上报错kubectl: Permission denied
java·kubernetes·jenkins
ch.ju1 小时前
Java程序设计(第3版)第四章——继承的特点
java·开发语言
Chase_______2 小时前
【Java杂项】Arrays.asList、List.of 和 new ArrayList:集合可变性避坑
java·windows·list
发际线向北2 小时前
0x07 深入了解JVM虚拟机(JVM异常处理)
java