Java并发编程进阶:Future与CompletableFuture深度解析与实战
你好呀!今天咱们来聊聊Java并发编程里的两个“狠角色”:Future
和 CompletableFuture
。别担心,我会尽量用大白话给你讲明白,保证你能听懂,还能用得上。
1. 为啥要用Future和CompletableFuture?
你想啊,咱们平时写程序,很多时候都是一步一步来的,就像这样:
// 1. 先煮饭
boilRice();
// 2. 再炒菜
cookVegetables();
// 3. 最后吃饭
eat();
煮饭的时候,你就只能干等着,啥也干不了。要是煮饭要半小时,你就得干等半小时!这多浪费时间啊!
在Java里,这种情况就叫“同步阻塞”。“同步”就是按顺序一步步来,“阻塞”就是上一步没做完,下一步就得等着。
那有没有办法让煮饭、炒菜同时进行呢?当然有!这就需要用到多线程了。
// 创建一个线程来煮饭
Thread boilRiceThread = new Thread(() -> boilRice());
boilRiceThread.start();
// 同时,主线程可以去炒菜
cookVegetables();
// ... 等煮饭线程结束,再吃饭
boilRiceThread.join(); // 等待煮饭线程完成
eat();
这样,煮饭和炒菜就可以同时进行了,大大节省了时间。但是,新的问题又来了:你怎么知道饭煮好了呢?boilRiceThread.join()
会一直阻塞,直到煮饭线程完成。如果我想在煮饭的同时,再做点别的事情呢?
这时候,Future
就闪亮登场了!
2. Future:异步计算的“期货”
Future
,顾名思义,就是“未来”。它代表了一个异步计算的“未来结果”。你可以把它想象成一张“期货合约”,你现在拿不到结果,但是将来某个时候,你可以凭这张“合约”去取结果。
怎么用呢?看代码:
// 创建一个线程池
ExecutorService executor = Executors.newFixedThreadPool(2);
// 提交一个煮饭的任务,返回一个Future对象
Future<String> boilRiceFuture = executor.submit(() -> {
// 模拟煮饭耗时
Thread.sleep(3000);
return "香喷喷的米饭";
});
// 同时,主线程可以去炒菜
cookVegetables();
// 想知道饭好了没?问问Future
if (boilRiceFuture.isDone()) {
// 饭好了,可以取结果了
String rice = boilRiceFuture.get();
System.out.println(rice);
} else {
// 饭还没好,可以先干点别的
System.out.println("饭还没好,先喝口水...");
}
// ... 等需要吃饭的时候,再来取结果
String rice = boilRiceFuture.get(); // 如果饭还没好,这里会阻塞
eat(rice);
// 关闭线程池
executor.shutdown();
看到了吧?Future
就像一个“占位符”,你先拿着,等需要的时候再来取结果。isDone()
方法可以用来检查任务是否完成,get()
方法用来获取结果(如果任务还没完成,get()
方法会阻塞)。
3. Future的局限性
Future
虽然好用,但也有一些局限性:
- 不能手动完成:
Future
表示的任务一旦开始,你就没办法手动设置它的结果,只能被动等待它完成。 - 阻塞式获取结果:
get()
方法是阻塞的,如果任务还没完成,你就得一直等着。 - 不支持异步回调:
Future
不能设置回调函数,当任务完成后,你没办法自动得到通知。 - 难以组合多个
Future
: 如果你有多个Future
,想把它们的结果组合起来,或者按照某种顺序执行它们,用Future
会很麻烦。
为了解决这些问题,Java 8 引入了 CompletableFuture
。
4. CompletableFuture:更强大的异步编程工具
CompletableFuture
,你可以把它理解为“可完成的Future”,或者“增强版的Future”。它不仅提供了 Future
的所有功能,还增加了许多强大的新特性,让异步编程更加灵活、高效。
4.1 手动完成
CompletableFuture<String> future = new CompletableFuture<>();
// 在未来的某个时刻,手动设置结果
future.complete("手动设置的结果");
// 获取结果
String result = future.get(); // result = "手动设置的结果"
4.2 异步回调
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
// 模拟耗时操作
Thread.sleep(3000);
return "异步计算的结果";
});
// 当任务完成后,自动执行回调函数
future.thenAccept(result -> {
System.out.println("收到结果:" + result);
});
// 主线程可以继续做其他事情
System.out.println("主线程继续执行...");
// ...
thenAccept
就是一个回调函数,当 future
完成时,它会自动被调用,你可以在回调函数里处理结果,而不需要主动去 get()
。
4.3 组合多个CompletableFuture
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "World");
// 将两个Future的结果组合起来
CompletableFuture<String> combinedFuture = future1.thenCombine(future2, (s1, s2) -> s1 + " " + s2);
// 获取组合后的结果
String result = combinedFuture.get(); // result = "Hello World"
thenCombine
可以将两个 CompletableFuture
的结果组合起来,形成一个新的 CompletableFuture
。
4.4 异常处理
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
// 模拟一个异常
if (true) {
throw new RuntimeException("计算出错!");
}
return "正常结果";
});
// 处理异常
future.exceptionally(ex -> {
System.out.println("发生异常:" + ex.getMessage());
return "备用结果";
});
// 获取结果(如果发生异常,会得到备用结果)
String result = future.get(); // result = "备用结果"
exceptionally
可以让你处理 CompletableFuture
中发生的异常。
4.5 更多用法
CompletableFuture
还提供了很多其他强大的功能,比如:
thenApply
:对CompletableFuture
的结果进行转换。thenCompose
:将一个CompletableFuture
的结果作为另一个CompletableFuture
的输入。allOf
:等待多个CompletableFuture
全部完成。anyOf
:只要多个CompletableFuture
中有一个完成,就返回。- ......
5. 实战案例
说了这么多,咱们来个实战案例,看看 CompletableFuture
怎么用。
假设我们要开发一个电商网站,用户下单后,我们需要做以下几件事:
- 检查库存。
- 扣减库存。
- 生成订单。
- 发送短信通知用户。
这些操作可以并行执行,以提高效率。我们可以用 CompletableFuture
来实现:
// 检查库存
CompletableFuture<Boolean> checkStockFuture = CompletableFuture.supplyAsync(() -> {
// 模拟检查库存
System.out.println("检查库存...");
return true; // 假设库存充足
});
// 扣减库存
CompletableFuture<Void> deductStockFuture = checkStockFuture.thenAcceptAsync(stockAvailable -> {
if (stockAvailable) {
// 模拟扣减库存
System.out.println("扣减库存...");
}
});
// 生成订单
CompletableFuture<String> createOrderFuture = checkStockFuture.thenApplyAsync(stockAvailable -> {
if (stockAvailable) {
// 模拟生成订单
System.out.println("生成订单...");
return "订单号:123456";
} else {
return null;
}
});
// 发送短信
CompletableFuture<Void> sendNotificationFuture = createOrderFuture.thenAcceptAsync(orderId -> {
if (orderId != null) {
// 模拟发送短信
System.out.println("发送短信通知用户,订单号:" + orderId);
}
});
// 等待所有任务完成
CompletableFuture.allOf(deductStockFuture, sendNotificationFuture).join();
System.out.println("下单完成!");
在这个例子中,我们使用了 CompletableFuture
的 supplyAsync
、thenAcceptAsync
、thenApplyAsync
和 allOf
方法,将多个异步任务组合起来,形成一个完整的下单流程。这些任务可以并行执行,大大提高了程序的效率。
6. 总结
Future
和 CompletableFuture
是Java并发编程中非常重要的两个工具,它们可以帮助我们编写高效、响应迅速的异步程序。CompletableFuture
在 Future
的基础上,提供了更强大的功能,让异步编程更加灵活、方便。
希望这篇文章能帮助你理解 Future
和 CompletableFuture
,并在实际开发中灵活运用它们。如果你还有什么问题,欢迎随时提问!