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 的局限性。通过链式调用和任务组合,它让代码更优雅、高效。在互联网场景中,如并行查询数据库,它能显著提升性能。理解其原理和局限性,可以帮助开发者在合适场景下发挥其最大价值。

相关推荐
Asthenia04121 小时前
编译原理基础:LL(1) 文法与 LL(1) 分析法
后端
Asthenia04122 小时前
编译原理基础:FIRST 集合与 FOLLOW 集合的构造与差异
后端
Asthenia04122 小时前
编译原理基础:FOLLOW 集合与 LL(1) 文法条件
后端
Asthenia04122 小时前
编译原理基础:FIRST 集合与提取公共左因子
后端
欧宸雅2 小时前
Clojure语言的持续集成
开发语言·后端·golang
Bruce_Liuxiaowei3 小时前
基于Flask的DeepSeek~学术研究领域智能辅助系统设计与实现
后端·python·flask·deepseek
Asthenia04123 小时前
面试官问:你谈谈网络协议栈是什么?你觉得Java工程师需要了解哪些部分?
后端
穿林鸟3 小时前
Spring Boot项目信创国产化适配指南
java·spring boot·后端
褚翾澜3 小时前
Haskell语言的NoSQL
开发语言·后端·golang