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

相关推荐
wowocpp4 分钟前
spring boot Controller 和 RestController 的区别
java·spring boot·后端
后青春期的诗go9 分钟前
基于Rust语言的Rocket框架和Sqlx库开发WebAPI项目记录(二)
开发语言·后端·rust·rocket框架
freellf15 分钟前
go语言学习进阶
后端·学习·golang
全栈派森2 小时前
云存储最佳实践
后端·python·程序人生·flask
CircleMouse2 小时前
基于 RedisTemplate 的分页缓存设计
java·开发语言·后端·spring·缓存
獨枭4 小时前
使用 163 邮箱实现 Spring Boot 邮箱验证码登录
java·spring boot·后端
维基框架4 小时前
Spring Boot 封装 MinIO 工具
java·spring boot·后端
秋野酱4 小时前
基于javaweb的SpringBoot酒店管理系统设计与实现(源码+文档+部署讲解)
java·spring boot·后端
☞无能盖世♛逞何英雄☜4 小时前
Flask框架搭建
后端·python·flask
进击的雷神4 小时前
Perl语言深度考查:从文本处理到正则表达式的全面掌握
开发语言·后端·scala