CompletableFuture学习

CompletableFuture学习

(个人学习记录)

学习CompletableFuture主要是为了异步操作。

引言:为什么需要 CompletableFuture?

在 Java 8 之前,我们处理异步任务依赖 Future 接口,但它有两个致命缺陷:

  1. 无法链式处理结果,必须通过 get() 阻塞获取,或手动轮询,代码臃肿且低效;

  2. 缺乏异常处理机制,一旦任务抛出异常,只能在 get() 时捕获,容错性差;

  3. 不支持多任务协同,无法便捷实现 "等待所有任务完成" 或 "任一任务完成" 的场景。

CompletableFuture 的出现彻底解决了这些问题 ------ 它实现了 Future 和 CompletionStage 接口,既是异步任务的结果容器,又是流程编排工具,让异步编程像同步代码一样简洁优雅。

本文将从基础用法到实战场景,带你全面掌握 CompletableFuture。

一、核心概念:CompletableFuture 是什么?

CompletableFuture 是 Java 8 引入的异步编程工具类,核心定位:

  • 结果容器:存储异步任务的执行结果(成功 / 失败);

  • 流程编排器:支持链式调用、多任务组合,无需手动管理线程;

  • 线程池友好:默认使用 ForkJoinPool.commonPool(),也支持自定义线程池,灵活控制资源。

它的核心优势:非阻塞 + 链式调用 + 异常处理 + 多任务协同,让异步代码可读性、可维护性大幅提升。

二、基础用法:创建 CompletableFuture 实例

CompletableFuture 的创建方式分 3 类,覆盖不同场景:

1. 直接创建(已完成 / 失败的任务)

适合快速返回已知结果或模拟异常场景:

java 复制代码
// 1. 已完成的任务(直接返回结果)
CompletableFuture<String> successFuture = CompletableFuture.completedFuture("操作成功");
String result = successFuture.join(); // 无需阻塞,直接获取结果

// 2. 已失败的任务(直接抛出异常)
CompletableFuture<String> failFuture = CompletableFuture.failedFuture(
    new RuntimeException("网络请求超时")
);
// 后续获取结果时会触发异常

2. 异步执行无返回值任务(runAsync)

适合不需要返回结果的异步操作(如日志打印、数据同步),底层是 Runnable:

java 复制代码
// 方式1:使用默认线程池(ForkJoinPool.commonPool())
CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> {
    System.out.println("异步执行无返回值任务(默认线程池)");
    // 模拟耗时操作(如文件写入)
    try { Thread.sleep(1000); } catch (InterruptedException e) {}
});

// 方式2:使用自定义线程池(推荐,避免默认线程池资源竞争)
ExecutorService customExecutor = Executors.newFixedThreadPool(3);
CompletableFuture<Void> future2 = CompletableFuture.runAsync(() -> {
    System.out.println("异步执行无返回值任务(自定义线程池)");
}, customExecutor);

3. 异步执行有返回值任务(supplyAsync)

适合需要返回结果的异步操作(如数据库查询、接口调用),底层是 Supplier:

java 复制代码
// 默认线程池,返回 String 类型结果
CompletableFuture<String> future3 = CompletableFuture.supplyAsync(() -> {
    // 模拟耗时查询(如查询用户信息)
    try { Thread.sleep(1500); } catch (InterruptedException e) {
        throw new RuntimeException("任务被中断", e);
    }
    return "用户ID:10086";
});

// 自定义线程池(推荐生产环境使用)
CompletableFuture<Integer> future4 = CompletableFuture.supplyAsync(() -> {
    return 100 + 200; // 模拟计算任务,返回 300
}, customExecutor);

💡 注意:自定义线程池需手动关闭(executor.shutdown()),避免资源泄露。

三、核心技巧:链式处理异步结果

CompletableFuture 的灵魂在于 链式调用------ 一个任务完成后,自动触发下一个任务,无需阻塞等待。

所有链式方法均返回新的 CompletableFuture,支持无限级联,核心方法分 3 类:

1. 处理结果(有返回值):thenApply

接收前一个任务的结果,处理后返回新结果,适合 "结果转换" 场景:

java 复制代码
// 场景:查询用户信息 → 转换为用户DTO → 加密敏感字段
CompletableFuture<String> userFuture = CompletableFuture.supplyAsync(() -> {
    return "原始用户信息:id=10086,name=张三"; // 第一步:查询原始数据
})
.thenApply(originInfo -> {
    return originInfo.replace("原始", "转换后"); // 第二步:转换格式
})
.thenApply(dto -> {
    return "加密后:" + dto.hashCode(); // 第三步:加密处理
});

// 最终结果:加密后:xxxx( hashCode 值)

2. 消费结果(无返回值):thenAccept

接收前一个任务的结果,仅消费(如打印、存储),无返回值:

java 复制代码
CompletableFuture.supplyAsync(() -> "异步任务结果")
.thenAccept(result -> {
    System.out.println("消费结果:" + result); // 输出:消费结果:异步任务结果
});

3. 执行后续任务(忽略结果):thenRun

不关心前一个任务的结果,仅执行后续操作(如发送通知):

java 复制代码
CompletableFuture.supplyAsync(() -> {
    // 模拟文件上传
    return "文件URL";
})
.thenRun(() -> {
    System.out.println("文件上传完成,发送通知给用户");
});

4. 异步链式:带 Async 后缀的方法

上述 thenApply/thenAccept/thenRun 默认使用前一个任务的线程或当前线程执行,若需异步执行后续任务,可使用带 Async 后缀的方法:

java 复制代码
CompletableFuture.supplyAsync(() -> "hello")
// 用自定义线程池异步处理结果
.thenApplyAsync(s -> s + " world", customExecutor)
// 再用自定义线程池异步消费
.thenAcceptAsync(System.out::println, customExecutor);

💡 关键区别:

  • 无 Async:后续任务在当前线程或前任务线程执行(同步执行);

  • 有 Async:后续任务在指定线程池(或默认线程池)异步执行。

四、异常处理:优雅解决异步任务报错

异步任务中抛出的异常不会直接暴露,需通过专门的方法捕获,核心有 2 种方式:

1. exceptionally:捕获异常,返回默认值

类似 try-catch,仅在前面任务失败时执行,返回默认结果:

java 复制代码
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    // 模拟任务失败
    throw new RuntimeException("数据库连接失败");
})
.exceptionally(ex -> {
    // 捕获异常,返回默认值
    System.out.println("异常原因:" + ex.getMessage());
    return "默认用户信息";
});

// 最终结果:默认用户信息

2. handle:成功 / 失败都处理

无论前面任务成功还是失败,都会执行,同时获取结果和异常:

java 复制代码
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "hello")
.handle((result, ex) -> {
    if (ex != null) {
        // 异常处理
        return "处理异常:" + ex.getMessage();
    } else {
        // 成功处理
        return result + " handle";
    }
});

// 最终结果:hello handle

💡 推荐使用 handle:覆盖所有场景,容错性更强。

五、进阶用法:多任务组合

CompletableFuture 支持多个异步任务的协同,核心有 2 个方法:allOf 和 anyOf。

1. allOf:等待所有任务完成

接收多个 CompletableFuture,返回一个新的 CompletableFuture,所有任务都完成后才会完成(适合 "批量处理" 场景):

java 复制代码
// 场景:批量查询3个服务的数据,全部查询完成后汇总
CompletableFuture<String> serviceA = CompletableFuture.supplyAsync(() -> "服务A数据");
CompletableFuture<String> serviceB = CompletableFuture.supplyAsync(() -> "服务B数据");
CompletableFuture<String> serviceC = CompletableFuture.supplyAsync(() -> "服务C数据");

// 等待所有任务完成
CompletableFuture<Void> allFuture = CompletableFuture.allOf(serviceA, serviceB, serviceC);

// 所有任务完成后,汇总结果
allFuture.thenRun(() -> {
    try {
        String a = serviceA.get();
        String b = serviceB.get();
        String c = serviceC.get();
        System.out.println("汇总结果:" + a + "," + b + "," + c);
    } catch (Exception e) {
        e.printStackTrace();
    }
});

⚠️ 注意:allOf 的返回结果是 Void,需通过原始 Future 的 get() 或 join() 获取各自结果。

2. anyOf:等待任一任务完成

接收多个 CompletableFuture,返回一个新的 CompletableFuture,任一任务完成后就会完成(适合 "兜底方案" 场景):

java 复制代码
// 场景:查询主服务和备用服务,哪个先返回就用哪个
CompletableFuture<String> mainService = CompletableFuture.supplyAsync(() -> {
    try { Thread.sleep(300); } catch (InterruptedException e) {}
    return "主服务数据";
});
CompletableFuture<String> backupService = CompletableFuture.supplyAsync(() -> {
    try { Thread.sleep(100); } catch (InterruptedException e) {}
    return "备用服务数据";
});

// 等待任一任务完成
CompletableFuture<Object> anyFuture = CompletableFuture.anyOf(mainService, backupService);

// 第一个完成的任务结果
anyFuture.thenAccept(result -> {
    System.out.println("使用最快返回的数据:" + result); // 输出:备用服务数据
});

六、获取结果:get () vs join ()

当需要主动获取 CompletableFuture 的结果时,有 2 个方法:

方法 异常处理方式 适用场景
get() 声明抛出 InterruptedException 和 ExecutionException,需显式捕获 需要精细处理异常的场景
join() 不声明异常,异常包装为 CompletionException(运行时异常) 无需显式捕获,简化代码

示例:

java 复制代码
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "结果");

// 使用 get():需捕获异常
try {
    String result1 = future.get();
} catch (InterruptedException | ExecutionException e) {
    e.printStackTrace();
}

// 使用 join():无需捕获,异常会直接抛出(运行时异常)
String result2 = future.join();

💡 推荐使用 join():代码更简洁,且异常可通过 exceptionally/handle 提前处理,避免显式捕获。

七、实战场景:CompletableFuture 解决实际问题

场景 1:并行查询多个接口,汇总结果

需求:查询用户的订单列表、优惠券列表、积分信息,并行执行,全部完成后返回给前端。

java 复制代码
// 自定义线程池(核心线程数=3,避免资源竞争)
ExecutorService executor = Executors.newFixedThreadPool(3);

// 1. 并行查询3个接口
CompletableFuture<List<Order>> orderFuture = CompletableFuture.supplyAsync(() -> orderService.queryOrders(userId), executor);
CompletableFuture<List<Coupon>> couponFuture = CompletableFuture.supplyAsync(() -> couponService.queryCoupons(userId), executor);
CompletableFuture<Integer> pointFuture = CompletableFuture.supplyAsync(() -> pointService.queryPoints(userId), executor);

// 2. 等待所有任务完成,汇总结果
CompletableFuture<UserInfoDTO> resultFuture = CompletableFuture.allOf(orderFuture, couponFuture, pointFuture)
.handle((voidResult, ex) -> {
    if (ex != null) {
        throw new RuntimeException("查询用户信息失败", ex);
    }
    // 汇总结果
    return new UserInfoDTO(
        orderFuture.join(),
        couponFuture.join(),
        pointFuture.join()
    );
});

// 3. 返回结果(Spring Boot 中可直接返回 CompletableFuture)
return resultFuture;

场景 2:异步处理文件上传,回调通知

需求:用户上传文件后,异步处理文件(转格式、压缩),处理完成后发送短信通知。

java 复制代码
@PostMapping("/upload")
public CompletableFuture<String> uploadFile(MultipartFile file) {
    ExecutorService executor = Executors.newSingleThreadExecutor();

    // 1. 异步处理文件(无返回值)
    CompletableFuture<Void> processFuture = CompletableFuture.runAsync(() -> {
        fileService.convertFormat(file); // 转格式
        fileService.compress(file); // 压缩
    }, executor);

    // 2. 处理完成后发送通知
    return processFuture.thenApplyAsync(voidResult -> {
        smsService.sendNotification("文件处理完成");
        return "上传成功,已通知用户";
    }, executor);
}

八、注意事项与最佳实践

  1. 自定义线程池:避免使用默认的 ForkJoinPool.commonPool(),尤其是在高并发场景,容易导致线程资源竞争;

  2. 线程池关闭:手动创建的线程池需在任务完成后关闭(executor.shutdown()),或使用 try-with-resources 自动关闭;

  3. 避免过度异步:CPU 密集型任务不适合异步(会增加线程切换开销),仅推荐 IO 密集型任务(如网络请求、文件读写);

  4. 异常全覆盖:所有异步任务都需通过 exceptionally/handle 处理异常,避免 "静默失败";

  5. 结果获取时机:尽量通过链式方法处理结果,而非主动调用 get()/join() 阻塞线程。

总结

CompletableFuture 是 Java 异步编程的 "瑞士军刀",它让原本复杂的异步流程变得清晰、简洁:

  • 无需手动管理线程,专注业务逻辑;

  • 链式调用 + 异常处理,代码可读性大幅提升;

  • 多任务组合(allOf/anyOf),轻松应对复杂场景。

掌握 CompletableFuture 后,可以优雅地处理 IO 密集型任务、并行计算、兜底方案等场景,让程序性能和可维护性更上一层楼。

相关推荐
盐焗西兰花2 小时前
鸿蒙学习实战之路-ArkTS循环渲染_ForEach使用指南
学习·华为·harmonyos
巧克力味的桃子2 小时前
单链表 - 有序插入并输出学习笔记
笔记·学习
jiayong232 小时前
知识库概念与核心价值01
java·人工智能·spring·知识库
皮皮林5513 小时前
告别 OOM:EasyExcel 百万数据导出最佳实践(附开箱即用增强工具类)
java
kylezhao20193 小时前
C# 语言基础(变量、数据类型、流程控制、面向对象编程)
开发语言·计算机视觉·c#·visionpro
咯哦哦哦哦3 小时前
WSL + ubantu22.04 + 远程桌面闪退+黑屏闪退解决
linux·开发语言
翩若惊鸿_3 小时前
【无标题】
开发语言·c#
Da Da 泓3 小时前
多线程(七)【线程池】
java·开发语言·线程池·多线程
坚持学习前端日记3 小时前
软件开发完整流程详解
学习·程序人生·职场和发展·创业创新