掌握CompletableFuture,提升你的代码效率!

在这里插入图片描述

1 CompletableFuture与线程池之间有什么关系?

CompletableFuture 和线程池的关系是,CompletableFuture 用来定义和管理异步任务,而线程池则负责实际执行这些任务。

想象一下,CompletableFuture 是一个能够在未来某个时刻完成任务的代表。它可以让代码更容易处理异步操作,也就是任务在后台运行,不会阻塞主线程。

在进行耗时的网络请求或者复杂的计算时,就可以用 CompletableFuture 来处理这些操作,并在任务完成时取得结果。

线程池则是负责实际执行这些任务的地方。它可以被看作是一组可以执行任务的工作线程。这些线程在后台待命,等待被分配任务。当任务到达线程池时,线程池会从这些线程中选择一个,分配任务,然后开始执行。

创建一个 CompletableFuture 时,通常需要指定一个线程池来处理实际的任务。

// 创建了一个包含四个线程的线程池
ExecutorService executor = Executors.newFixedThreadPool(4);

// 使用 supplyAsync 方法创建一个 CompletableFuture 时,可以传递一个 Executor,这个 Executor 就是线程池的实现
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    // 模拟耗时的任务
    try {
        Thread.sleep(2000);
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }
    return "Task Completed";
}, executor);

线程池的作用在于管理这些线程的生命周期和调度,避免每次执行任务时都创建新的线程,这样可以提高效率,减少资源消耗。线程池中的线程会在任务完成后继续等待新的任务,从而避免了频繁的线程创建和销毁开销。

当任务完成时,CompletableFuture 会获取任务的结果。使用线程池执行的任务可以利用 CompletableFuture 提供的各种功能,比如处理异常、组合多个异步任务等:

// 在任务完成时打印结果,如果有异常发生,则会打印错误信息
future.thenAccept(result -> {
    System.out.println("Result: " + result);
}).exceptionally(ex -> {
    System.err.println("Error: " + ex.getMessage());
    return null;
});

2 如何优化CompletableFuture的性能?

优化 CompletableFuture 的性能需要关注几个关键方面。

一个重要的策略是合理使用线程池。可以创建一个固定大小的线程池来避免过多的线程切换开销,通过 Executors.newFixedThreadPool(int nThreads) 创建线程池,nThreads 的值应与系统核心数匹配,确保资源得到有效利用。

团队工作效率不仅仅取决于每个人的能力,还与分配的资源有关。如果团队人数太多或太少,都会影响整体效率。

默认情况下,supplyAsync 使用的是公共的 ForkJoinPool,但在高负载时,这可能成为瓶颈,可以考虑创建并配置自定义的线程池来适应具体的任务负载:

public static void main(String[] args) {
        ExecutorService customThreadPool = Executors.newFixedThreadPool(10);

        CompletableFuture.supplyAsync(() -> fetchData("http://api.com"), customThreadPool)
            .thenAccept(result -> System.out.println(result));
        
        // 关闭线程池
        customThreadPool.shutdown();
    }

    private static String fetchData(String url) {
        // 模拟 HTTP 请求
        return "Response from " + url;
    }

处理多个异步任务时,合并操作能够减少资源浪费。使用 CompletableFuture.allOf(futures) 可以等待所有给定的 CompletableFuture 完成,这样可以减少不必要的等待时间。如果只需要等待其中一个任务完成,CompletableFuture.anyOf(futures) 是更合适的选择,它会在第一个完成的任务完成后立即返回结果。

在一个团队中,如果每个人都独立完成自己的任务,最终合并结果的过程可能会很麻烦。

如果需要同时处理多个异步任务,可以使用 allOf 合并它们。比如,如果有多个 API 请求可以并行处理,可以像这样将它们合并:

public static void main(String[] args) {
        CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> fetchData("http://api1.com"));
        CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> fetchData("http://api2.com"));

        CompletableFuture<Void> allOf = CompletableFuture.allOf(future1, future2);

        allOf.thenRun(() -> {
            try {
                System.out.println("Result from API 1: " + future1.get());
                System.out.println("Result from API 2: " + future2.get());
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
    }

    private static String fetchData(String url) {
        // 模拟 HTTP 请求
        return "Response from " + url;
    }

避免阻塞操作也至关重要。在异步操作中,阻塞线程会影响整体性能。尽量将耗时操作放在异步任务内部,而不是在主线程中进行等待,这样可以减少线程占用,提高执行效率。

想象一个在办公室里的团队成员,他们需要处理多项任务。如果有人在处理一个任务时不停地打电话查询进度,这就像是阻塞操作,导致其他任务也被拖慢。

当需要链式处理多个异步结果时,thenCompose 方法可以提供更高的性能和清晰度。thenCompose 用于处理返回的 CompletableFuture,可以避免多层嵌套,使得代码更简洁。

CompletableFuture.supplyAsync(() -> {
    // 第一个异步任务
    return "Hello";
}).thenCompose(result -> {
    // 处理第一个任务的结果,并返回另一个 CompletableFuture
    return CompletableFuture.supplyAsync(() -> result + " World");
}).thenAccept(System.out::println);

处理异常时,使用 exceptionallyhandle 方法可以提升代码的健壮性。exceptionally 可以处理任务执行中的异常,handle 既处理正常结果也处理异常。这样能确保任务在遇到问题时仍能正常运行或提供有效的错误信息。

任务拆分也是一种优化策略。将大任务拆分成多个小任务可以提高并发性和效率。这样做能更好地利用线程池,并且可以更精确地控制任务的执行。

监控和调优是优化性能的基础。使用工具来监控线程池的使用情况和 CompletableFuture 的执行状态,可以及时发现并解决瓶颈问题,通过实际测试和分析,调整线程池大小和任务拆分策略,从而实现最佳性能。

3 实际项目中,以并行执行多个HTTP请求为例,你会如何优雅使用CompletableFuture 解决问题?

在实际项目中,使用 CompletableFuture 来并行执行多个 HTTP 请求可以显著提高效率。比如,考虑一个场景,需要从多个 API 获取数据。

分析:创建 CompletableFuture 实例并执行异步 HTTP 请求时,可以使用 CompletableFuture.supplyAsync 方法。这个方法接受一个 Supplier,在这个 Supplier 中执行实际的 HTTP 请求。每个请求都可以用一个 CompletableFuture 处理,从而实现并行执行。

如何处理多个 HTTP 请求

import java.util.concurrent.CompletableFuture;
import java.util.List;

public class AsyncHttpRequests {

    public static void main(String[] args) {
        // 包含了多个要请求的 URL
        List<String> urls = List.of("http://api1.com", "http://api2.com", "http://api3.com");

        // 创建了一组 CompletableFuture 实例,每一个都负责异步执行一个 HTTP 请求
        // fetchData(url) 方法模拟了实际的 HTTP 请求逻辑,这里可以用真实的 HTTP 客户端代替
        List<CompletableFuture<String>> futures = urls.stream()
            .map(url -> CompletableFuture.supplyAsync(() -> fetchData(url)))
            .toList();

        // 等待所有异步请求完成,接收一个 CompletableFuture 数组,并在所有请求完成后继续执行后续操作。
        CompletableFuture<Void> allOf = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
        // thenApply 方法将所有请求结果汇集起来,提取每个 CompletableFuture 的结果并汇集成一个列表
        CompletableFuture<List<String>> allOfResult = allOf.thenApply(v -> 
            futures.stream()
                   .map(CompletableFuture::join)
                   .toList()
        );

        // 将所有结果打印到控制台
        allOfResult.thenAccept(results -> {
            results.forEach(System.out::println);
        });
    }

    private static String fetchData(String url) {
        // 模拟 HTTP 请求,这里应使用实际的 HTTP 请求逻辑
        return "Response from " + url;
    }
}

使用 CompletableFuture 可以优雅地处理多个异步任务,通过合并结果和处理异常,可以确保程序在高负载时表现稳定。

必须从过去的错误学习教训而非依赖过去的成功

评论 260
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

忆~遂愿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值