JUC:CompletableFuture 详细用法讲解

CompletableFuture 详细用法讲解

1. CompletableFuture 是什么?来自哪里?有什么用?

CompletableFuture 是 Java 8 引入的一个强大的异步编程工具,位于 java.util.concurrent 包中。它是对传统 Future 接口的扩展,旨在提供更灵活、更强大的异步任务处理能力。

作用

  • 异步执行:允许任务在后台线程中运行,主线程无需等待即可继续执行。
  • 结果处理:提供丰富的 API 来处理异步任务的结果(如成功或失败时的回调)。
  • 任务组合:支持将多个异步任务组合起来,处理复杂的依赖关系。
  • 异常处理:内置机制来捕获和处理异步任务中的异常。

与传统的 Future 相比,CompletableFuture 解决了 Future 的几个痛点:

  • Future 无法主动完成,只能被动等待 get() 结果,阻塞线程。
  • Future 不支持回调机制,无法方便地处理结果或异常。
  • Future 无法轻松组合多个任务。

CompletableFuture 的出现填补了这些空白,使 Java 的异步编程更加现代化和函数式。


2. 如何使用 CompletableFuture?它解决了什么问题?

使用 CompletableFuture,你可以通过静态方法(如 supplyAsyncrunAsync)启动异步任务,并通过链式调用(如 thenApplythenAcceptexceptionally 等)处理结果或异常。

基本用法示例

java 复制代码
import java.util.concurrent.CompletableFuture;

public class CompletableFutureDemo {
    public static void main(String[] args) {
        // 异步计算一个值
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(1000); // 模拟耗时操作
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "Hello, CompletableFuture!";
        });

        // 当任务完成时打印结果
        future.thenAccept(System.out::println);

        System.out.println("主线程继续执行...");
    }
}

输出:

erlang 复制代码
主线程继续执行...
Hello, CompletableFuture!(1秒后)

解决的问题

  1. 阻塞问题 :传统 Future.get() 会阻塞线程,而 CompletableFuture 提供非阻塞的回调方式。
  2. 回调地狱:通过链式调用替代嵌套回调,代码更简洁。
  3. 组合困难 :支持多任务并行执行并等待所有任务完成(如 allOf)。

3. 结合互联网场景:异步查询简历信息

在互联网应用中,异步处理非常常见。例如,一个招聘网站需要从数据库中查询某份简历的获奖经历(RewardExp)、资格证书(Credential)和工作种类(ResumeJobs)。这些查询是独立的,可以并行执行以提高性能。

以下是使用 CompletableFuture 的实现:

java 复制代码
import java.util.List;
import java.util.concurrent.CompletableFuture;

public class ResumeService {
    public void fetchResumeDetails(Resume resume) {
        // 查询获奖经历
        LambdaQueryWrapper<RewardExp> rewardExpQuery = new LambdaQueryWrapper<RewardExp>()
                .eq(RewardExp::getResumeId, resume.getId())
                .eq(RewardExp::getDelFlag, NORMAL)
                .orderByDesc(RewardExp::getDate);
        CompletableFuture<List<RewardExp>> rewardExpFuture = CompletableFuture.supplyAsync(() ->
                rewardExpMapper.selectList(rewardExpQuery)
        );

        // 查询资格证书
        LambdaQueryWrapper<Credential> credentialQuery = new LambdaQueryWrapper<Credential>()
                .eq(Credential::getResumeId, resume.getId())
                .eq(Credential::getDelFlag, NORMAL)
                .orderByDesc(Credential::getDate);
        CompletableFuture<List<Credential>> credentialFuture = CompletableFuture.supplyAsync(() ->
                credentialMapper.selectList(credentialQuery)
        );

        // 查询工种
        LambdaQueryWrapper<ResumeJobs> jobsQuery = new LambdaQueryWrapper<ResumeJobs>()
                .eq(ResumeJobs::getResumeId, resume.getId())
                .eq(ResumeJobs::getDelFlag, NORMAL);
        CompletableFuture<List<ResumeJobs>> jobsFuture = CompletableFuture.supplyAsync(() ->
                resumeJobsMapper.selectList(jobsQuery)
        );

        // 等待所有任务完成
        CompletableFuture.allOf(rewardExpFuture, credentialFuture, jobsFuture).join();

        // 获取结果
        List<RewardExp> rewards = rewardExpFuture.getNow(null);
        List<Credential> credentials = credentialFuture.getNow(null);
        List<ResumeJobs> jobs = jobsFuture.getNow(null);

        // 处理结果(例如返回给前端)
        System.out.println("获奖经历: " + rewards);
        System.out.println("资格证书: " + credentials);
        System.out.println("工种: " + jobs);
    }
}

效果

  • 并行执行:三个查询同时进行,总耗时接近于最慢的单个查询时间,而不是串行执行的总和。
  • 非阻塞 :主线程可以继续处理其他逻辑,直到需要结果时才调用 join()getNow()
  • 场景适用性:这种方式在高并发场景(如 REST API)中能显著提升响应速度。

4. CompletableFuture 的原理、依赖和失效场景

原理

CompletableFuture 基于 Java 的 ForkJoinPool(默认线程池)执行异步任务。它通过以下机制工作:

  1. 任务提交supplyAsyncrunAsync 将任务提交到线程池。
  2. 完成通知 :任务完成后,结果或异常存储在 CompletableFuture 对象中。
  3. 链式处理 :通过内部的回调链表(Completion 对象),在任务完成时触发后续操作(如 thenApply)。
  4. 事件驱动 :依赖 Java 的并发工具(如 LockSupport)实现高效的线程协调。

依赖

  • 线程池 :默认使用 ForkJoinPool.commonPool(),也可以通过自定义 Executor 指定线程池。
  • Java 并发包 :依赖 java.util.concurrent 中的工具类。

失效场景

  1. 线程池耗尽 :如果线程池资源不足(例如任务过多或线程被长时间占用),新任务会被阻塞或拒绝。
    • 解决办法:自定义线程池并监控其状态。
  2. 任务阻塞 :如果异步任务中有同步 I/O 或无限循环,线程会被占用,导致性能下降。
    • 解决办法:确保任务适合异步执行,避免阻塞操作。
  3. 异常未处理 :若未使用 exceptionally 或类似方法捕获异常,任务失败可能无声无息。
    • 解决办法:始终添加异常处理逻辑。
  4. 不适合短任务 :对于非常短暂的任务,异步调用的开销可能超过收益。
    • 解决办法:评估任务耗时,短任务直接同步执行。

总结

CompletableFuture 是 Java 异步编程的利器,解决了传统 Future 的局限性。通过链式调用和任务组合,它让代码更优雅、高效。在互联网场景中,如并行查询数据库,它能显著提升性能。理解其原理和局限性,可以帮助开发者在合适场景下发挥其最大价值。

相关推荐
Nturmoils9 分钟前
内网穿透原来这么简单:Natapp 从注册到公网访问完整教程
后端
寻求出路的程序媛21 分钟前
分布式 & 高性能 & 高可用 体系、学习重点、面试点
分布式·后端·面试·性能优化
掘金者阿豪32 分钟前
Let‘s Encrypt 证书到底会不会自动续期?从一次服务器迁移后的证书排查说起
后端
Zane199438 分钟前
手写"判断key存不存在"的模板代码太烦?一文讲透 collections 四件套
后端·python
joinwell5239 分钟前
Agent 中断后,原任务如何安全接管?从恢复标记到效果事实
人工智能·后端·架构
sp421 小时前
羽量级的 Java Bean 实体校验器
后端
魔兽大山哥1 小时前
【NL2SQL 实战 07】LIMIT 不是小事:默认 100 行背后的产品判断
后端
qq_339191141 小时前
go cpu占比高排查,cpu100%排查,go pprof cpu命令
开发语言·后端·golang
笃行3501 小时前
异构数据同步如何真正做到“数据无忧“?——解读 KFS 的全周期一致性校验与修复能力
后端
云边有个稻草人1 小时前
异构数据同步如何保证数据不出错:KFS全周期校验与修复实践
后端