Appearance
Future与CompletableFuture
前置知识
在阅读本章前,你需要了解基本的Java线程和并发知识,例如Thread,Runnable接口,以及基础的同步概念。
为什么需要 Future 和 CompletableFuture?
在实际开发中,我们经常有一些任务需要执行,但它们可能比较耗时,比如远程服务调用、文件I/O或复杂计算。一直等着任务完成再做后续操作,会让程序卡住——用户就像站在红绿灯前一直等绿灯一样,效率非常低下。
这时,我们希望让程序**“继续干别的事”**,异步地执行这些任务,等它们完成后再获取结果甚至继续处理。 这就是 Future 和更强大的 CompletableFuture 的作用。
1. 认识 Future:异步任务的“占位符”
Future接口就像你去餐厅点了份饭,得到一个“叫号牌”。你可以拿着它去做别的事,饭做好了再来取。Future代表一个异步计算的结果,是一个“任务占位符”。
1.1 为什么需要它?
Java早期的线程库只支持启动线程,线程结束没法直接返回结果。Future模式解决了这个问题,让你能通过它去获取异步任务的结果。
1.2 简单用法
用ExecutorService提交一个Callable任务,返回Future,你可以调用get()方法获取结果(该方法会阻塞直到结果准备好)。
java
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class FutureExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newSingleThreadExecutor();
Callable<String> task = () -> {
Thread.sleep(2000); // 模拟耗时操作
return "执行结果";
};
Future<String> futureResult = executor.submit(task);
System.out.println("任务已提交,等待结果...");
try {
String result = futureResult.get(); // 阻塞等待结果
System.out.println("任务完成,结果是:" + result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
executor.shutdown();
}
}这段代码做了什么?
- 创建单线程执行器(线程池)
- 提交了一个模拟耗时的
Callable任务,返回Future - 主线程继续执行,调用
futureResult.get()等待结果 - 任务完成后打印结果
- 关闭执行器释放资源
1.3 小结
Future允许你异步启动任务- 通过
get()阻塞等待任务完成 - 不能直接串联多个Future,处理流程不够灵活
2. CompletableFuture:更强大的异步处理王者
你可能发现,单纯的Future有点单调,只能调用get(),没法组成复杂的异步流水线。Java 8引入的CompletableFuture像给异步任务插上了智能的大脑——它不仅能完成Future的功能,还支持链式调用、组合多任务和统一异常处理。
2.1 为什么它更酷?
CompletableFuture内置了丰富的异步方法,例如:
thenApply:任务完成后转换结果thenCompose:串行组合异步任务thenCombine:合并两个独立异步任务结果exceptionally:异常处理
让异步逻辑像流水线一样清晰可读,避免回调地狱(callback hell),写起代码像 “讲故事”。
2.2 基础示例:从0开始用CompletableFuture
java
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class CompletableFutureSimple {
public static void main(String[] args) {
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(1500); // 模拟耗时操作
} catch (InterruptedException e) {
e.printStackTrace();
}
return "Hello CompletableFuture";
});
System.out.println("任务启动,要等结果...");
try {
String result = future.get(); // 阻塞等待结果
System.out.println("任务结果:" + result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}这段代码做了什么?
- 使用
supplyAsync异步调用返回一个结果 - 主线程继续执行
future.get()获取结果,等待任务完成- 输出异步任务的字符串结果
这段和第一个例子类似,不过使用了CompletableFuture,语义更现代化。
3. 链式调用与组合:异步任务编排的艺术
任务之间往往有关联,一旦一个任务完成,还得接着做转换或合并多个异步计算结果。CompletableFuture能让我们像流水线一样组织这些步骤,实现异步编排。
3.1 thenApply:结果转换
java
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class CompletableFutureChain {
public static void main(String[] args) {
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Java")
.thenApply(result -> result + " CompletableFuture") // 结果转换
.thenApply(String::toUpperCase); // 再转大写
try {
System.out.println(future.get()); // 输出:JAVA COMPLETABLEFUTURE
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}这段代码做了什么?
- 异步产生字符串
"Java" - 通过
thenApply给字符串添加文本 - 再通过
thenApply转成大写 - 最终输出经过两次转换的字符串
3.2 thenCompose:串联异步任务
假设有两个任务,第二个任务依赖第一个的结果。
java
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class CompletableFutureThenCompose {
public static CompletableFuture<String> fetchUserName() {
return CompletableFuture.supplyAsync(() -> "Tom");
}
public static CompletableFuture<Integer> fetchUserAge(String name) {
return CompletableFuture.supplyAsync(() -> 20 + name.length());
}
public static void main(String[] args) {
CompletableFuture<Integer> userAgeFuture = fetchUserName()
.thenCompose(name -> fetchUserAge(name));
try {
System.out.println("用户年龄:" + userAgeFuture.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}写代码的故事:
- 第一个异步任务获取用户名
- 接着基于用户名启动第二个异步任务获取年龄
thenCompose让我们“接力”下去,不用写嵌套回调
3.3 thenCombine:合并两个并行任务
如果有两个任务彼此独立,但结果要合并处理:
java
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class CompletableFutureThenCombine {
public static CompletableFuture<Integer> fetchPrice() {
return CompletableFuture.supplyAsync(() -> 100);
}
public static CompletableFuture<Integer> fetchTax() {
return CompletableFuture.supplyAsync(() -> 20);
}
public static void main(String[] args) {
CompletableFuture<Integer> totalPriceFuture = fetchPrice()
.thenCombine(fetchTax(), (price, tax) -> price + tax);
try {
System.out.println("总价(含税):" + totalPriceFuture.get()); // 输出120
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}这段代码展示了如何并行发起两个请求,等两个结果都准备好后合并计算。
⚠️ 常见陷阱
阻塞调用陷阱
尽量避免直接调用get(),虽然它能阻塞等待结果,但失去了异步的意义,容易造成线程阻塞甚至死锁。推荐结合链式操作处理结果。异常处理遗漏
CompletableFuture异步链中,如果不处理异常,后续链条将无法正常执行。一定要用exceptionally或handle捕获异常,保证流程健壮。线程池资源用尽
默认的ForkJoinPool.commonPool()适合轻量任务,大任务或者阻塞调用应使用自定义线程池,避免线程饥饿。
💡 实战建议
尽量避免阻塞调用,利用链式方法处理结果。 这会让异步代码更流畅,更高效。
结合业务需求选择合适的线程池执行异步任务。 默认线程池适用于大多数场景,但长时间阻塞的任务,建议自定义线程池。
善用异常处理接口,提高代码鲁棒性。 异步编程难免遇到错误,记得给每个链条都赋予“保护伞”。
合理拆分任务,组织清晰的异步流水线。 分块、挂载回调,让复杂的业务逻辑有条理。
🔍 深入理解:CompletableFuture的内部原理简要介绍
CompletableFuture底层基于ForkJoinPool的线程池实现异步任务调度,使用内部的“任务依赖图”来跟踪和执行各个阶段任务,支持任务间复杂的依赖关系。当一个任务完成时,它会激活下游依赖继续执行, 以实现高效的并发调度。
这种设计使它在性能和扩展性上相较传统线程模型有较大优势,也是Java异步编程的主流方案。
小结
Future是异步任务的“占位符”,可以阻塞等待结果,但限制较多CompletableFuture功能丰富,支持链式调用、任务组合和异常处理- 合理使用链式操作让异步任务代码简洁清晰,避免阻塞
- 结合线程池资源,注意异常安全和性能优化是关键
一次异步之旅从Future的“叫号牌”开始,到CompletableFuture的“智能流水线”成就你的高并发能力。祝你异步编程顺利,写出优雅干净又高效的代码!
完整代码示例合集
java
// Future 基础示例
import java.util.concurrent.*;
public class FutureExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newSingleThreadExecutor();
Callable<String> task = () -> {
Thread.sleep(2000); // 模拟耗时操作
return "执行结果";
};
Future<String> futureResult = executor.submit(task);
System.out.println("任务已提交,等待结果...");
try {
String result = futureResult.get(); // 阻塞等待结果
System.out.println("任务完成,结果是:" + result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
executor.shutdown();
}
}java
// CompletableFuture 基础示例
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class CompletableFutureSimple {
public static void main(String[] args) {
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "Hello CompletableFuture";
});
System.out.println("任务启动,要等结果...");
try {
String result = future.get();
System.out.println("任务结果:" + result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}java
// CompletableFuture 链式调用示例
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class CompletableFutureChain {
public static CompletableFuture<String> fetchUserName() {
return CompletableFuture.supplyAsync(() -> "Tom");
}
public static CompletableFuture<Integer> fetchUserAge(String name) {
return CompletableFuture.supplyAsync(() -> 20 + name.length());
}
public static void main(String[] args) {
CompletableFuture<Integer> userAgeFuture = fetchUserName()
.thenCompose(name -> fetchUserAge(name));
try {
System.out.println("用户年龄:" + userAgeFuture.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}java
// CompletableFuture 任务合并示例
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class CompletableFutureThenCombine {
public static CompletableFuture<Integer> fetchPrice() {
return CompletableFuture.supplyAsync(() -> 100);
}
public static CompletableFuture<Integer> fetchTax() {
return CompletableFuture.supplyAsync(() -> 20);
}
public static void main(String[] args) {
CompletableFuture<Integer> totalPriceFuture = fetchPrice()
.thenCombine(fetchTax(), (price, tax) -> price + tax);
try {
System.out.println("总价(含税):" + totalPriceFuture.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}欢迎继续探索Java异步编程的更多可能,下章我们可以一起聊聊CompletionStage中的更多高级用法,比如allOf、anyOf和自定义线程池哦!
