文章目录

📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌
📙 作者: 编程技术圈(哇哥面试陪跑)
👉 欢迎关注、分享、评论
✔️ 持续分享更多干货内容
🌐🌏🌎➕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 的进阶用法 —— 异常处理怎么搞?批量异步任务怎么控制?线程池怎么配置才不踩坑?让你从入门到精通,异步代码想怎么写就怎么写!

169万+

被折叠的 条评论
为什么被折叠?



