CompletableFuture 实战:电商商品详情页与微服务性能优化秘籍
你好呀!我是你们的编程小助手“代码小旋风”!今天咱们来聊聊 Java 并发编程中的一个神器——CompletableFuture
。相信不少小伙伴在实际开发中都遇到过这样的场景:电商网站的商品详情页加载慢、微服务架构中服务调用耗时长,导致系统吞吐量上不去,用户体验也大打折扣。别担心,CompletableFuture
就是解决这些问题的利器!
1. 为什么需要 CompletableFuture?
在传统的 Java 并发编程中,我们通常使用 Future
来获取异步任务的执行结果。但是,Future
有一些明显的局限性:
- 获取结果不方便:
Future.get()
方法会阻塞当前线程,直到任务完成并返回结果。如果任务执行时间较长,会导致线程长时间阻塞,降低系统吞吐量。 - 不支持回调:
Future
无法设置回调函数,在任务完成后自动执行某些操作。我们只能通过轮询或者阻塞的方式来获取结果,非常不灵活。 - 难以组合多个异步任务: 如果我们需要将多个异步任务的结果组合起来,使用
Future
会非常麻烦,代码会变得冗长且难以维护。
CompletableFuture
的出现,正是为了解决这些问题。它提供了更强大的功能和更灵活的 API,让我们可以更轻松地编写高性能的并发程序。
2. CompletableFuture 核心概念与 API
CompletableFuture
实现了 Future
和 CompletionStage
接口,它既可以表示一个异步任务的结果,也可以表示一个异步任务的执行阶段。
2.1 创建 CompletableFuture 对象
我们可以通过以下几种方式创建 CompletableFuture
对象:
CompletableFuture.completedFuture(value)
: 创建一个已完成的CompletableFuture
对象,其值为给定的value
。CompletableFuture<String> future = CompletableFuture.completedFuture("Hello, CompletableFuture!");
CompletableFuture.runAsync(runnable)
: 创建一个异步执行的CompletableFuture
对象,执行给定的Runnable
任务(无返回值)。CompletableFuture<Void> future = CompletableFuture.runAsync(() -> { // 模拟耗时操作 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Task executed asynchronously."); });
CompletableFuture.supplyAsync(supplier)
: 创建一个异步执行的CompletableFuture
对象,执行给定的Supplier
任务(有返回值)。CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> { // 模拟耗时操作 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } return "Task executed asynchronously and returned a result."; });
2.2 获取异步任务的结果
CompletableFuture
提供了多种获取异步任务结果的方法:
get()
: 阻塞当前线程,直到任务完成并返回结果。与Future.get()
类似,但CompletableFuture.get()
在任务执行过程中抛出异常时,会抛出CompletionException
,而不是ExecutionException
。join()
: 与get()
类似,但join()
不会抛出受检异常(Checked Exception),而是抛出非受检异常(Unchecked Exception)。getNow(valueIfAbsent)
: 立即返回结果,如果任务已完成,则返回结果;否则,返回给定的valueIfAbsent
值。complete(value)
: 如果任务尚未完成,则将任务的结果设置为给定的value
,并触发依赖该任务的其他任务执行。如果任务已完成,则该方法不执行任何操作。completeExceptionally(ex)
: 如果任务尚未完成,则将任务的结果设置为给定的异常ex
,并触发依赖该任务的其他任务执行。如果任务已完成,则该方法不执行任何操作。
2.3 异步任务的回调
CompletableFuture
最大的优势之一就是支持回调。我们可以在任务完成后自动执行某些操作,而无需手动阻塞或轮询。
thenApply(fn)
/thenApplyAsync(fn)
: 在任务完成后,将任务的结果作为参数传递给给定的函数fn
,并返回一个新的CompletableFuture
对象,其值为函数fn
的返回值。CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello") .thenApply(s -> s + ", World!") .thenApply(String::toUpperCase); System.out.println(future.join()); // 输出 HELLO, WORLD!
thenAccept(consumer)
/thenAcceptAsync(consumer)
: 在任务完成后,将任务的结果作为参数传递给给定的消费者consumer
,不返回任何结果。CompletableFuture.supplyAsync(() -> "Hello") .thenAccept(s -> System.out.println("Computation returned: " + s));
thenRun(action)
/thenRunAsync(action)
: 在任务完成后,执行给定的Runnable
任务action
,不关心任务的结果。CompletableFuture.supplyAsync(() -> "Hello") .thenRun(() -> System.out.println("Computation finished."));
thenApplyAsync
、thenAcceptAsync
和 thenRunAsync
方法会使用默认的线程池(ForkJoinPool.commonPool())来执行回调函数。我们也可以指定自定义的线程池。
2.4 组合多个异步任务
CompletableFuture
提供了强大的 API 来组合多个异步任务,实现复杂的并发逻辑。
thenCompose(fn)
/thenComposeAsync(fn)
: 在一个CompletableFuture
对象完成后,将其结果作为参数传递给一个函数,该函数返回另一个CompletableFuture
对象。//获取店铺基本信息 public CompletableFuture<Shop> getShopInfo(long shopId) { return CompletableFuture.supplyAsync(()->{ Shop shop = new Shop(); shop.setShopId(shopId); shop.setName("店铺" + shopId); //模拟耗时 try { Thread.sleep(100); } catch (InterruptedException e) { throw new RuntimeException(e); } return shop; }); } //获取店铺商品列表 public CompletableFuture<List<Product>> getProductList(long shopId){ return CompletableFuture.supplyAsync(()->{ List<Product> productList = new ArrayList<>(); productList.add(new Product(1,"商品1")); productList.add(new Product(2,"商品2")); //模拟耗时 try { Thread.sleep(200); } catch (InterruptedException e) { throw new RuntimeException(e); } return productList; }); }
public void testThenCompose(){
long shopId = 123L;
CompletableFuture<List<Product>> completableFuture = getShopInfo(shopId).thenCompose(this::getProductList);
List<Product> products = completableFuture.join();
System.out.println(products);
}
```
thenCombine(other, fn)
/thenCombineAsync(other, fn)
: 将两个CompletableFuture
对象的结果组合起来,并将组合后的结果作为参数传递给给定的函数fn
。//获取店铺基本信息 public CompletableFuture<Shop> getShopInfo(long shopId) { return CompletableFuture.supplyAsync(()->{ Shop shop = new Shop(); shop.setShopId(shopId); shop.setName("店铺" + shopId); //模拟耗时 try { Thread.sleep(100); } catch (InterruptedException e) { throw new RuntimeException(e); } return shop; }); } //获取店铺评分 public CompletableFuture<Double> getShopScore(long shopId){ return CompletableFuture.supplyAsync(()->{ //模拟耗时 try { Thread.sleep(50); } catch (InterruptedException e) { throw new RuntimeException(e); } return 4.5d; }); } public void thenCombineTest(){ long shopId = 123L; CompletableFuture<String> completableFuture = getShopInfo(shopId).thenCombine(getShopScore(shopId), (shop, score) -> { return shop.getName() + "的评分是:" + score; }); System.out.println(completableFuture.join()); }
allOf(cfs)
: 等待所有给定的CompletableFuture
对象完成。//获取店铺基本信息 public CompletableFuture<Shop> getShopInfo(long shopId) { return CompletableFuture.supplyAsync(()->{ Shop shop = new Shop(); shop.setShopId(shopId); shop.setName("店铺" + shopId); //模拟耗时 try { Thread.sleep(100); } catch (InterruptedException e) { throw new RuntimeException(e); } return shop; }); } //获取店铺商品列表 public CompletableFuture<List<Product>> getProductList(long shopId){ return CompletableFuture.supplyAsync(()->{ List<Product> productList = new ArrayList<>(); productList.add(new Product(1,"商品1")); productList.add(new Product(2,"商品2")); //模拟耗时 try { Thread.sleep(200); } catch (InterruptedException e) { throw new RuntimeException(e); } return productList; }); } //获取店铺评分 public CompletableFuture<Double> getShopScore(long shopId){ return CompletableFuture.supplyAsync(()->{ //模拟耗时 try { Thread.sleep(50); } catch (InterruptedException e) { throw new RuntimeException(e); } return 4.5d; }); } public void allOfTest(){ long shopId = 123L; CompletableFuture<Shop> shopInfoFuture = getShopInfo(shopId); CompletableFuture<List<Product>> productListFuture = getProductList(shopId); CompletableFuture<Double> shopScoreFuture = getShopScore(shopId); CompletableFuture<Void> allFuture = CompletableFuture.allOf(shopInfoFuture, productListFuture, shopScoreFuture); // 等待所有任务完成 allFuture.join(); // 获取结果 Shop shopInfo = shopInfoFuture.join(); List<Product> productList = productListFuture.join(); Double shopScore = shopScoreFuture.join(); System.out.println("店铺信息:" + shopInfo); System.out.println("商品列表:" + productList); System.out.println("店铺评分:" + shopScore); }
anyOf(cfs)
: 只要有一个给定的CompletableFuture
对象完成,就返回该对象的结果。
3. 电商商品详情页实战
现在,我们来看一个具体的例子:如何使用 CompletableFuture
优化电商网站的商品详情页加载。
假设一个商品详情页需要加载以下信息:
- 商品基本信息(名称、价格、描述等)
- 商品图片列表
- 商品库存信息
- 商品评价列表
- 商家信息
这些信息可能来自不同的服务,如果串行加载,会导致页面加载时间过长。我们可以使用 CompletableFuture
并发加载这些信息,大大缩短页面加载时间。
// 模拟商品基本信息服务
CompletableFuture<ProductInfo> getProductInfo(long productId) {
return CompletableFuture.supplyAsync(() -> {
// 模拟数据库查询
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
return new ProductInfo(productId, "商品名称", 99.9, "商品描述");
});
}
// 模拟商品图片列表服务
CompletableFuture<List<String>> getProductImages(long productId) {
return CompletableFuture.supplyAsync(() -> {
// 模拟网络请求
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
return Arrays.asList("图片1.jpg", "图片2.jpg", "图片3.jpg");
});
}
// 模拟商品库存信息服务
CompletableFuture<Integer> getProductStock(long productId) {
return CompletableFuture.supplyAsync(() -> {
// 模拟数据库查询
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
return 100;
});
}
// 模拟商品评价列表服务
CompletableFuture<List<Comment>> getProductComments(long productId) {
return CompletableFuture.supplyAsync(() -> {
// 模拟网络请求
try {
Thread.sleep(150);
} catch (InterruptedException e) {
e.printStackTrace();
}
return Arrays.asList(new Comment("用户1", "好评!"), new Comment("用户2", "不错!"));
});
}
// 模拟商家信息服务
CompletableFuture<ShopInfo> getShopInfo(long shopId) {
return CompletableFuture.supplyAsync(() -> {
// 模拟数据库查询
try {
Thread.sleep(80);
} catch (InterruptedException e) {
e.printStackTrace();
}
return new ShopInfo(shopId, "商家名称");
});
}
// 加载商品详情页
ProductDetailPage loadProductDetailPage(long productId, long shopId) {
CompletableFuture<ProductInfo> productInfoFuture = getProductInfo(productId);
CompletableFuture<List<String>> productImagesFuture = getProductImages(productId);
CompletableFuture<Integer> productStockFuture = getProductStock(productId);
CompletableFuture<List<Comment>> productCommentsFuture = getProductComments(productId);
CompletableFuture<ShopInfo> shopInfoFuture = getShopInfo(shopId);
// 使用 allOf 等待所有任务完成
CompletableFuture<Void> allFuture = CompletableFuture.allOf(
productInfoFuture,
productImagesFuture,
productStockFuture,
productCommentsFuture,
shopInfoFuture
);
// 等待所有任务完成,并获取结果
return allFuture.thenApply(v -> {
ProductInfo productInfo = productInfoFuture.join();
List<String> productImages = productImagesFuture.join();
Integer productStock = productStockFuture.join();
List<Comment> productComments = productCommentsFuture.join();
ShopInfo shopInfo = shopInfoFuture.join();
return new ProductDetailPage(productInfo, productImages, productStock, productComments, shopInfo);
}).join();
}
// 模拟数据类
class ProductInfo {
long productId;
String name;
double price;
String description;
public ProductInfo(long productId, String name, double price, String description) {
this.productId = productId;
this.name = name;
this.price = price;
this.description = description;
}
// ... 省略 getter 和 setter
}
class Comment {
String username;
String content;
public Comment(String username, String content) {
this.username = username;
this.content = content;
}
// ... 省略 getter 和 setter
}
class ShopInfo {
long shopId;
String shopName;
public ShopInfo(long shopId, String shopName) {
this.shopId = shopId;
this.shopName = shopName;
}
// ... 省略 getter 和 setter
}
class ProductDetailPage {
ProductInfo productInfo;
List<String> productImages;
Integer productStock;
List<Comment> productComments;
ShopInfo shopInfo;
public ProductDetailPage(ProductInfo productInfo, List<String> productImages, Integer productStock, List<Comment> productComments, ShopInfo shopInfo) {
this.productInfo = productInfo;
this.productImages = productImages;
this.productStock = productStock;
this.productComments = productComments;
this.shopInfo = shopInfo;
}
// ... 省略 getter 和 setter
}
class Product{
long productId;
String name;
public Product(long productId, String name) {
this.productId = productId;
this.name = name;
}
// ... 省略 getter 和 setter
}
class Shop{
long shopId;
String name;
// ... 省略 getter 和 setter
public void setShopId(long shopId) {
this.shopId = shopId;
}
public void setName(String name) {
this.name = name;
}
public long getShopId() {
return shopId;
}
public String getName() {
return name;
}
}
通过CompletableFuture
,我们将商品详情页的多个加载任务并行化,显著减少了页面加载时间,提升了用户体验。假设原来串行加载需要 580ms (100+200+50+150+80),现在并行加载只需要 200ms(取决于耗时最长的任务)。
4. 微服务架构中的性能优化
在微服务架构中,服务之间的调用通常是同步阻塞的。如果一个服务调用另一个服务,而另一个服务响应缓慢,会导致调用方线程长时间阻塞,影响整个系统的性能。
CompletableFuture
可以帮助我们实现异步的服务调用,避免线程阻塞,提高系统的吞吐量。
例如,假设我们有一个订单服务,需要调用用户服务来获取用户信息。我们可以使用 CompletableFuture
异步调用用户服务:
// 模拟用户服务
CompletableFuture<UserInfo> getUserInfo(long userId) {
return CompletableFuture.supplyAsync(() -> {
// 模拟网络请求
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
return new UserInfo(userId, "用户名");
});
}
// 订单服务
Order createOrder(long userId, long productId) {
// 异步调用用户服务
CompletableFuture<UserInfo> userInfoFuture = getUserInfo(userId);
// 创建订单
Order order = new Order(userId, productId);
// 等待用户信息获取完成,并设置到订单中
userInfoFuture.thenAccept(userInfo -> order.setUserInfo(userInfo));
//thenAccept是阻塞的,这里只是为了演示。
//实际上这里应该返回CompletableFuture<Order>,然后调用方如果需要结果可以进行join()
return order;
}
// 模拟数据类
class UserInfo {
long userId;
String username;
public UserInfo(long userId, String username) {
this.userId = userId;
this.username = username;
}
// ... 省略 getter 和 setter
}
class Order {
long userId;
long productId;
UserInfo userInfo;
public Order(long userId, long productId) {
this.userId = userId;
this.productId = productId;
}
public void setUserInfo(UserInfo userInfo) {
this.userInfo = userInfo;
}
// ... 省略 getter 和 setter
}
通过异步调用,订单服务无需等待用户服务返回结果,可以继续处理其他请求,提高了系统的吞吐量。
5. 异常处理
在异步任务中,异常处理非常重要。CompletableFuture
提供了多种处理异常的方法:
exceptionally(fn)
: 当任务执行过程中发生异常时,会调用给定的函数fn
,并将异常作为参数传递给该函数。exceptionally
方法返回一个新的CompletableFuture
对象,其值为函数fn
的返回值。CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> { throw new RuntimeException("Computation error!"); }).exceptionally(ex -> { System.err.println("Error: " + ex.getMessage()); return "Default Value"; }); System.out.println(future.join()); // 输出 Default Value
handle(fn)
: 无论任务是否成功完成,都会调用给定的函数fn
,并将任务的结果或异常作为参数传递给该函数。handle
方法返回一个新的CompletableFuture
对象,其值为函数fn
的返回值。CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello") .handle((result, ex) -> { if (ex != null) { System.err.println("Error: " + ex.getMessage()); return "Default Value"; } else { return result.toUpperCase(); } }); System.out.println(future.join()); // 输出 HELLO
6. 总结与注意事项
CompletableFuture
是 Java 并发编程的一大利器,它可以帮助我们编写高性能、高吞吐量的应用程序。在使用 CompletableFuture
时,需要注意以下几点:
- 选择合适的线程池: 默认情况下,
CompletableFuture
使用ForkJoinPool.commonPool()
线程池。在某些情况下,我们可能需要自定义线程池,例如,为不同的任务分配不同的线程池,或者限制线程池的大小。 - 避免过度使用: 虽然
CompletableFuture
很强大,但过度使用会导致代码复杂性增加,反而降低性能。我们应该根据实际情况,合理使用CompletableFuture
。 - **注意CompletableFuture的阻塞方法:**虽然CompletableFuture是异步编程的利器,但是其中仍然存在阻塞的方法,例如get()和join(),不正确的使用仍然会导致性能问题。
- 异常处理: 异步任务中的异常处理非常重要。我们需要使用
exceptionally
或handle
方法来处理异常,避免程序崩溃。 - 上下文传递: 在异步任务中,如果需要传递上下文信息(例如,用户信息、请求 ID 等),可以使用
ThreadLocal
或者自定义的上下文对象。
希望通过今天的分享,你能对 CompletableFuture
有更深入的了解,并在实际开发中灵活运用,打造高性能、高可用的 Java 应用!如果你有任何问题或者想了解更多关于 CompletableFuture
的知识,欢迎在评论区留言,我会尽力解答!