性能爆炸的多线程编排神器CompletableFuture
--楼兰 多线程并发编程,一直是程序员的一个硬伤。如何科学高效的对多线程任务进行编排,也是设计实现高效软件开发必要的功力。为了简化复杂场景下的线程编排,Java8新增了CompletableFuture类。ComletableFuture类提供了一套优雅的线程编排API,可以极大的简化业务调度模型,让程序员可以轻松写出高效的代码。
CompletableFuture简介
使用CompletableFuture,程序员可以很方便的控制线程的执行顺序。包括串行、并行或者多个线程的组合与转化功能。
CompletableFuture主要实现了两个关键接口:

-
Future:Future是一个代表异步计算结果的对象。它允许你从一个异步操作中获取结果(一旦结果可用),取消正在执行的计算,或者检查计算是否已经完成。Future 通常与 ExecutorService 结合使用来提交任务,并且是 java.util.concurrent 包的一部分.
-
CompletionStage:Completionstage 提供了更高级的功能和更好的可组合性。它是 Java 8中引入的,作为 java.util.concurrent 包的一部分,completionstage 允许你以声明式的方式组合和链接异步操作,而不需要显式地处理回调函数。
Completablefuture 的内部使用了基于 ForkJoinPool的线程池,这种线程池可以有效地调度和执行任务。
CompletableFuture 的非阻塞特性得益于其对任务完成的监听机制,当任务完成时,它会遍历所有注册的回调函数,并在合适的线程中执行这些回调。通过这种机制,CompletableFuture能够在任务完成后及时返回结果或触发后续处理還辑,而不会阻塞主线程的执行。
CompletableFuture常用方法
| 方法名称 | 作用描述 |
|---|---|
| supplyAsync | 静态方法,用于构建一个CompletableFuture 对象,并异步执行传入的参数,允许执行函数有返回值 |
| runAsync | 静态方法,用于构建一个CompletableFuture 对象,并异步执行传入函数,与supplyAsync的区别在于此方法传入的是Callable类型,仅执行,没有返回值 |
| get() | 等待CompletableFuture执行完成并获取其具体执行结果,可能会抛出异常,需要代码调用的地方手动try…catch进行处理。 |
| thenApply | 对CompletableFuture的执行后的结果进行追加处理,并将当前的CompletableFuture泛型对象更改为处理后新的对象类型,返回当前CompletableFuture对象 |
| thenCompose | 与thenApply类似,区别点在于:此方法的入参函数返回一个CompletableFuture类型对象 |
| thenAccept | 在所有异步任务完成后执行一系列操作,与thenApply类似,区别点在于thenApply返回void类型,没有具体结果输出,适合无需返回值的场景 |
| thenRun | 与thenAccept类似,区别点在于thenAccept可以将前面CompletableFuture执行的实际结果作为参数进行传入并使用,但是thenRun方法没有任何入参,只能执行一个Runnable函数,并且返回void类型 |
| handle | 与thenApply类似,区别点在于handle执行函数的入参有两个,一个是CompletableFuture执行的实际结果,一个是是Throwable对象,这样如果前面执行出现异常的时候,可以通过handle获取到异常并进行处理。 |
| whenComplete | 与handle类似,区别点在于whenComplete执行后无返回值。 |
| thenCombine | 将两个CompletableFuture对象组合起来进行下一步处理,可以拿到两个执行结果,并传给自己的执行函数进行下一步处理,最后返回一个新的CompletableFuture对象。 |
| thenAcceptBoth | 与thenCombine类似,区别点在于thenAcceptBoth传入的执行函数没有返回值,即thenAcceptBoth返回值为CompletableFuture。 |
| runAfterBoth | 等待两个CompletableFuture都执行完成后再执行某个Runnable对象,再执行下一个的逻辑,类似thenRun。 |
| applyToEither | 两个CompletableFuture中任意一个完成的时候,继续执行后面给定的新的函数处理。再执行后面给定函数的逻辑,类似thenApply。 |
| acceptEither | 两个CompletableFuture中任意一个完成的时候,继续执行后面给定的新的函数处理。再执行后面给定函数的逻辑,类似thenAccept。 |
| runAfterEither | 等待两个CompletableFuture中任意一个执行完成后再执行某个Runnable对象,可以理解为thenRun的升级版,注意与runAfterBoth对比理解。 |
| allOf | 静态方法,阻塞等待所有给定的CompletableFuture执行结束后,返回一个CompletableFuture结果。 |
| anyOf | 静态方法,阻塞等待任意一个给定的CompletableFuture对象执行结束后,返回一个CompletableFuture结果。 |
| get(long, TimeUnit) | 与get()相同,只是允许设定阻塞等待超时时间,如果等待超过设定时间,则会抛出异常终止阻塞等待。 |
| join() | 等待CompletableFuture执行完成并获取其具体执行结果,可能会抛出运行时异常,无需代码调用的地方手动try…catch进行处理。 |
多线程任务编排实战演练
现在定义三个数据插入的服务,用不同的线程阻塞时长模拟不同的业务耗时
public class UserService {
public Boolean insert(){
System.out.println("UserService insert begin");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("UserService insert end");
return true;
}
}
public class OrderService {
public Boolean insert(){
System.out.println("OrderService insert begin");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("OrderService insert end");
return true;
}
}
public class CartService {
public Boolean insert(){
System.out.println("CartService insert begin");
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("CartService insert end");
return true;
}
}
现在有个Controller需要调用这三个服务,插入数据。 这时候,不同的实现方式就会体现出巨大的性能差异。
- 不考虑并发,直接写业务
public void insert1(){
long start = System.currentTimeMillis();
UserService userService = new UserService();
OrderService orderService = new OrderService();
CartService cartService = new CartService();
Boolean userRes = userService.insert();
System.out.println("user查询耗时:"+(System.currentTimeMillis()-start));
Boolean orderRes = orderService.insert();
System.out.println("order查询耗时:"+(System.currentTimeMillis()-start));
Boolean cartRes = cartService.insert();
System.out.println("cart查询耗时:"+(System.currentTimeMillis()-start));
System.out.println("数据获取完成,userRes="+userRes+",orderRes="+orderRes+",cartRes="+cartRes);
System.out.println("总耗时:"+(System.currentTimeMillis()-start));
}
执行顺序就是依次插入各条数据,所以总耗时在9秒左右。
UserService insert begin
UserService insert end
user查询耗时:2006
OrderService insert begin
OrderService insert end
order查询耗时:5018
CartService insert begin
CartService insert end
cart查询耗时:9018
数据获取完成,userRes=true,orderRes=true,cartRes=true
总耗时:9032
- 通过CompletableFuture,改成线程并发执行
public void insert2() throws Exception{
long start = System.currentTimeMillis();
UserService userService = new UserService();
OrderService orderService = new OrderService();
CartService cartService = new CartService();
CompletableFuture<Boolean> userFuture = CompletableFuture.supplyAsync(userService::insert);
CompletableFuture<Boolean> orderFuture = CompletableFuture.supplyAsync(orderService::insert);
CompletableFuture<Boolean> cartFuture = CompletableFuture.supplyAsync(cartService::insert);
//每个Future并行执行
Boolean userRes = userFuture.get();
System.out.println("user查询耗时:"+(System.currentTimeMillis()-start));
Boolean orderRes = orderFuture.get();
System.out.println("order查询耗时:"+(System.currentTimeMillis()-start));
Boolean cartRes = cartFuture.get();
System.out.println("cart查询耗时:"+(System.currentTimeMillis()-start));
System.out.println("总耗时:"+(System.currentTimeMillis()-start));
//所有Future执行结束后返回
// CompletableFuture<Void> allFuture = CompletableFuture.allOf(userFuture, orderFuture, cartFuture);
// allFuture.get();
// System.out.println("总耗时:"+(System.currentTimeMillis()-start));
//任意一个Future执行结束就返回
// CompletableFuture<Object> anyFuture = CompletableFuture.anyOf(userFuture, orderFuture, cartFuture);
// anyFuture.get();
}
简单通过CompletableFuture就可以让三个任务全部并发执行,所以最终耗时就是单独耗时最长的那个任务。
UserService insert begin
OrderService insert begin
CartService insert begin
UserService insert end
user查询耗时:2008
OrderService insert end
order查询耗时:3007
CartService insert end
cart查询耗时:4008
总耗时:4008
- 通过CompletableFuture,不光可以控制线程执行方式,还可以实现任务编排
现在假设三个任务之间有前后顺序,需要在UserService执行完成后,再同时调用OrderService和CartService

使用CompletableFuture就可以很优雅的实现这种任务编排
public void insert3() throws Exception{
long start = System.currentTimeMillis();
UserService userService = new UserService();
OrderService orderService = new OrderService();
CartService cartService = new CartService();
//返回处理完成的任务,任务执行过程中会产生阻塞
CompletableFuture<Boolean> userFuture = CompletableFuture.completedFuture(userService.insert());
System.out.println("userRes = "+userFuture.get()+";耗时:"+(System.currentTimeMillis()-start));
//后面两个任务并行执行
CompletableFuture<Boolean> orderFuture = CompletableFuture.supplyAsync(orderService::insert);
CompletableFuture<Boolean> cartFuture = CompletableFuture.supplyAsync(cartService::insert);
CompletableFuture<Void> allFuture = CompletableFuture.allOf(orderFuture, cartFuture);
allFuture.get();
System.out.println("数据插入完成,总耗时:"+(System.currentTimeMillis()-start));
}
最终执行耗时6秒
UserService insert begin
UserService insert end
userRes = true;耗时:2004
OrderService insert begin
CartService insert begin
OrderService insert end
CartService insert end
数据插入完成,总耗时:6017
- 对任务进行并行与串行的混合编排
现在对任务进行更复杂的业务编排。希望orderService与cartService串行执行,而userService与这两个服务并行执行。

使用CompletableFuture同样可以很优雅的进行编排
public void insert4() throws Exception{
long start = System.currentTimeMillis();
UserService userService = new UserService();
OrderService orderService = new OrderService();
CartService cartService = new CartService();
CompletableFuture<Boolean> userFuture = CompletableFuture.supplyAsync(userService::insert);
CompletableFuture<Boolean> orderFuture = CompletableFuture.supplyAsync(orderService::insert);
CompletableFuture<Boolean> cartFuture = orderFuture.thenApply(orderRes -> cartService.insert());
CompletableFuture<Void> allFuture = CompletableFuture.allOf(userFuture, cartFuture);
allFuture.get();
System.out.println("总耗时:"+(System.currentTimeMillis()-start));
}
执行结果,总耗时7秒
UserService insert begin
OrderService insert begin
UserService insert end
OrderService insert end
CartService insert begin
CartService insert end
总耗时:7008

241

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



