【039】Future 坑哭了?CompletableFuture 让异步代码爽到飞起

在这里插入图片描述


📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌

📙 作者: 编程技术圈(哇哥面试陪跑)
👉 欢迎关注、分享、评论
✔️ 持续分享更多干货内容
🌐🌏🌎➕tcmeta, 欢迎沟通交流

📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌


零、引入

“接口响应从 200ms 飙到 2 秒!” 王二对着监控面板抓头发 —— 他用 Future 异步获取商品信息和库存,结果代码里全是 get () 阻塞,原本想提速的异步变成了 “伪异步”,领导催着下班前必须修复。隔壁哇哥啃着鸡腿路过,扫了眼代码笑出了声:“你这是拿异步的壳子装同步的核!赶紧换 CompletableFuture,链式调用不用等,性能直接翻倍,今天我手把手教你改,保准让你准时下班!”

点赞 + 关注,跟着哇哥和王二,用 “餐厅点餐” 的逻辑吃透 CompletableFuture 基础用法,下次再写异步代码被骂,我替你背锅!

在这里插入图片描述

一、王二的坑:Future 的 “阻塞陷阱” 有多坑?

在这里插入图片描述

王二的需求很简单:异步查询商品基本信息(耗时 50ms)和库存数量(耗时 80ms),然后合并结果返回。他用 Future 实现,代码看着没问题,一压测就崩。

➡️ 王二的 “伪异步” 代码(Future 坑版)

package cn.tcmeta.completables;


import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

/**
 * @author: laoren
 * @description: 王二的 “伪异步” 代码(Future 坑版)
 * @version: 1.0.0
 */
public class FutureDisasterSample {

    // 模拟商品服务:查询商品信息
    private static CompletableFuture<String> getProductInfo(String productId) {
        return CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.MILLISECONDS.sleep(50);
            } catch (InterruptedException _) {
            }
            return "商品ID:" + productId + ",名称:华为Mate70,价格:6999元";
        });
    }

    // 模拟库存服务:查询库存数量
    private static CompletableFuture<Integer> getStock(String productId) {
        return CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.MILLISECONDS.sleep(80);
            } catch (InterruptedException _) {
            }
            return 100; // 库存100件
        });
    }

    static void main() throws ExecutionException, InterruptedException {
        long start = System.currentTimeMillis();
        String productId = "P1001";

        // 异步调用,但王二用了阻塞的get()
        CompletableFuture<String> infoFuture = getProductInfo(productId);
        CompletableFuture<Integer> stockFuture = getStock(productId);

        // 坑1:get()阻塞等待,两个异步任务变成了串行执行
        String productInfo = infoFuture.get(); 
        Integer stock = stockFuture.get();     

        // 合并结果
        String result = productInfo + ",当前库存:" + stock + "件";
        System.out.println("查询结果:" + result);
        System.out.println("总耗时:" + (System.currentTimeMillis() - start) + "ms");
    }

}

在这里插入图片描述
王二委屈:“我明明用了异步方法,怎么还是串行的?”
在这里插入图片描述

哇哥把鸡腿骨一扔:“Future 的 get () 是‘阻塞炸弹’!调用后线程就停在这等结果,两个任务本来能并行(总耗时 80ms 左右),被你硬生生改成了串行。CompletableFuture 的核心就是‘不用等’,用链式调用把结果处理逻辑接在后面,任务完成自动执行,根本不用手动阻塞!”

二、用 “餐厅点餐” 讲透 CompletableFuture 核心优势

在这里插入图片描述

哇哥拿王二中午点餐的经历打比方,瞬间把抽象概念讲活了:

在这里插入图片描述

❓ CompletableFuture 的 3 大核心优势(王二记小本本)

  • 非阻塞回调:任务完成后自动执行后续逻辑,不用 get () 阻塞等待,就像餐厅服务员送餐上门;
  • 链式调用:多个异步任务可以串成流水线(比如 “查信息→查库存→合并结果”),代码简洁不臃肿;
  • 灵活组合:支持多任务并行、串行、任意一个完成触发等组合方式,比 Future 的手动协调高效 10 倍。

“简单说,Future 是‘你去取餐’,CompletableFuture 是‘餐送上门’,” 哇哥总结,“异步编程的核心就是‘不用等’,这才是 CompletableFuture 的精髓!”

三、改造代码:CompletableFuture 链式调用爽到飞起

哇哥只改了几行代码,把阻塞的 get () 换成链式调用,总耗时直接从 138ms 降到 86ms,性能直接翻倍!

✨ 优化后的代码(CompletableFuture 封神版)

package cn.tcmeta.completables;


import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

/**
 * @author: laoren
 * @description: 优化版:CompletableFuture链式调用,异步并行无阻塞
 * @version: 1.0.0
 */
public class CompletableFutureRescueSample {

    // 模拟商品服务:查询商品信息(和之前一样)
    private static CompletableFuture<String> getProductInfo(String productId) {
        return CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.MILLISECONDS.sleep(50);
            } catch (InterruptedException _) {
            }
            return "商品ID:" + productId + ",名称:华为Mate70,价格:6999元";
        });
    }

    // 模拟库存服务:查询库存数量(和之前一样)
    private static CompletableFuture<Integer> getStock(String productId) {
        return CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.MILLISECONDS.sleep(80);
            } catch (InterruptedException _) {
            }
            return 100; // 库存100件
        });
    }

    static void main() throws InterruptedException {
        long start = System.currentTimeMillis();
        String productId = "P1001";
        // 核心优化:链式调用,两个任务并行执行,结果自动合并
        CompletableFuture<String> resultFuture = getProductInfo(productId)
                // 当商品信息和库存都获取完成后,执行合并逻辑
                .thenCombine(getStock(productId), (info, stock) -> {
                    // 合并结果的逻辑
                    return info + ",当前库存:" + stock + "件";
                });

        // 这里可以做其他事情,不用阻塞等待
        System.out.println("主线程:发起异步请求后,我先干别的...");
        TimeUnit.MILLISECONDS.sleep(30); // 模拟主线程做其他工作
        System.out.println("主线程:别的活干完了,等结果...");

        // 获取最终结果(此时两个任务基本已完成,阻塞时间极短)
        String result = resultFuture.join(); // join()和get()类似,但不用抛checked异常
        System.out.println("查询结果:" + result);
        System.out.println("总耗时:" + (System.currentTimeMillis() - start) + "ms");
    }

}

运行结果:并行执行,耗时减半
在这里插入图片描述
王二直呼卧槽:“就换了个 thenCombine,耗时直接砍半?这链式调用也太香了!”

“这还只是基础操作,” 哇哥挑眉,“CompletableFuture 还有 thenAccept、thenRun、exceptionally 等一堆方法,能应对各种异步场景,比如任务完成后发通知、任务失败后降级处理,比 Future 灵活多了。

四、CompletableFuture 基本用法(王二的 “三板斧”)

哇哥怕王二记混,把最常用的 3 个方法总结成 “三板斧”,直接能覆盖 80% 的场景:
在这里插入图片描述

📒 发起异步任务:supplyAsync 和 runAsync

  • supplyAsync:有返回值的异步任务(比如查商品信息、查库存),对应Supplier接口;
  • runAsync:无返回值的异步任务(比如发通知、记录日志),对应Runnable接口。
// 1. 有返回值:supplyAsync
CompletableFuture<String> supplyFuture = CompletableFuture.supplyAsync(() -> {
    // 异步逻辑,返回结果
    return "有返回值的异步任务";
});

// 2. 无返回值:runAsync
CompletableFuture<Void> runFuture = CompletableFuture.runAsync(() -> {
    // 异步逻辑,无返回值
    System.out.println("无返回值的异步任务");
});

📢 任务完成后处理:thenAccept 和 thenRun

  • thenAccept : 接收任务结果,无返回值(比如打印结果、存数据库);
  • thenRun : 不接收任务结果,只在任务完成后执行(比如发通知)。
CompletableFuture.supplyAsync(() -> "查询结果")
        // 接收结果并处理(有输入,无输出)
        .thenAccept(result -> System.out.println("处理结果:" + result))
        // 不接收结果,任务完成后执行(无输入,无输出)
        .thenRun(() -> System.out.println("任务完成,发通知给用户"));

👉 多任务组合:thenCombine 和 thenCompose

  • thenCombine:两个独立任务都完成后,合并结果(比如查商品 + 查库存);
  • thenCompose:一个任务的结果作为另一个任务的输入(比如查商品 ID→根据 ID 查库存,串行依赖)
// 1. thenCombine:两个独立任务合并
CompletableFuture<String> infoFuture = getProductInfo("P1001");
CompletableFuture<Integer> stockFuture = getStock("P1001");
infoFuture.thenCombine(stockFuture, (info, stock) -> info + ",库存:" + stock);

// 2. thenCompose:任务依赖(先查商品ID,再查库存)
CompletableFuture<String> productIdFuture = CompletableFuture.supplyAsync(() -> "P1001");
productIdFuture.thenCompose(productId -> getStock(productId) // 用前一个结果作为参数
        .thenApply(stock -> "商品ID:" + productId + ",库存:" + stock));

五、总结:CompletableFuture 核心心法(王二编顺口溜)

在这里插入图片描述

王二把基础用法编成顺口溜,贴在显示器上:

  • 异步任务不用等,CompletableFuture 来敲门;
  • supply 有返回,run 无返回,按需选准不后悔;
  • accept 收结果,run 做收尾,组合任务用 combine;
  • 依赖任务用 compose,并行执行效率高

✔️ 哇哥的面试彩蛋

“面试时被问 Future 和 CompletableFuture 的区别,你就举餐厅点餐的例子,” 哇哥传授技巧,“然后写 thenCombine 的代码,讲清楚‘非阻塞回调’和‘灵活组合’两个核心优势 —— 面试官一听就知道你是真懂,不是背概念!我当年面字节,就靠这个知识点过了二面。”

关注我,下一篇咱们扒一扒 CompletableFuture 的进阶用法 —— 异常处理怎么搞?批量异步任务怎么控制?线程池怎么配置才不踩坑?让你从入门到精通,异步代码想怎么写就怎么写!
在这里插入图片描述

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值