CompletableFuture的概念
什么是CompletableFuture?
可完成的Future,不同于异步的Future依赖轮询获取结果,CompletableFuture可以在不同的阶段获取异步线程的执行结果,或者根据丰富的链式回调函数完成结果的多重转换
从接口的实现来看,CompletableFuture实现了Future以及CompletionStage两个接口,CompletionStage接口中支持多种方法,包括带有返回值的和不带返回值的,下面将对这些方法进行解释:
`CompletionStage<T>`接口定义了可能异步计算的一个阶段,当一个`CompletionStage`完成时,它可以触发另一个`CompletionStage`。以下是一些常用的方法:
1. `thenApply(Function<? super T,? extends U> fn)`: 当前阶段正常完成后,将结果作为参数传递给函数`fn`,并返回一个新的`CompletionStage`来表示函数`fn`的结果。
2. `thenAccept(Consumer<? super T> action)`: 当前阶段正常完成后,将结果作为参数传递给消费者`action`,并返回一个新的`CompletionStage`来表示`action`的完成。
3. `thenRun(Runnable action)`: 当前阶段正常完成后,运行给定的动作,并返回一个新的`CompletionStage`来表示`action`的完成。
4. `thenCombine(CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends V> fn)`: 当前阶段和另一个阶段都正常完成后,将这两个阶段的结果作为参数传递给函数`fn`,并返回一个新的`CompletionStage`来表示`fn`的结果。
5. `thenAcceptBoth(CompletionStage<? extends U> other, BiConsumer<? super T, ? super U> action)`: 当前阶段和另一个阶段都正常完成后,将这两个阶段的结果作为参数传递给消费者`action`,并返回一个新的`CompletionStage`来表示`action`的完成。
6. `applyToEither(CompletionStage<? extends T> other, Function<? super T, U> fn)`: 当前阶段和另一个阶段中任意一个先正常完成,就将该阶段的结果作为参数传递给函数`fn`,并返回一个新的`CompletionStage`来表示`fn`的结果。
7. `acceptEither(CompletionStage<? extends T> other, Consumer<? super T> action)`: 当前阶段和另一个阶段中任意一个先正常完成,就将该阶段的结果作为参数传递给消费者`action`,并返回一个新的`CompletionStage`来表示`action`的完成。
8. `runAfterEither(CompletionStage<?> other, Runnable action)`: 当前阶段和另一个阶段中任意一个先正常完成,就运行给定的动作,并返回一个新的`CompletionStage`来表示`action`的完成。
9. `runAfterBoth(CompletionStage<?> other, Runnable action)`: 当前阶段和另一个阶段都正常完成后,运行给定的动作,并返回一个新的`CompletionStage`来表示`action`的完成。
10. `whenComplete(BiConsumer<? super T, ? super Throwable> action)`: 当前阶段完成时(无论正常还是异常),将结果(如果正常完成)或异常(如果异常完成)作为参数传递给消费者`action`,并返回一个新的`CompletionStage`来表示`action`的完成。
11. `handle(BiFunction<? super T, Throwable, ? extends U> fn)`: 当前阶段完成时(无论正常还是异常),将结果(如果正常完成)或异常(如果异常完成)作为参数传递给函数`fn`,并返回一个新的`CompletionStage`来表示`fn`的结果。
CompletableFuture异步原理
CompletableFuture |
result |
stack |
CompletableFuture 中CompletationStage的运用
supplyAsync
简言之就是带有返回值的异步调用方法,其中有两个重载方法,可以根据是否传入线程池对象,决定使用内置的Fork线程池还是自定义的线程池
//使用默认内置线程池ForkJoinPool.commonPool(),根据supplier构建执行任务
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
//自定义线程,根据supplier构建执行任务
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)
//定义线程池
static
ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 10, 1, TimeUnit.HOURS, new LinkedBlockingDeque<>(),
new ThreadPoolExecutor.AbortPolicy());
主线程定义completableFuture的方法,使用我们上面定义的线程池,内部打印Thread.sleep 模拟线程执行的过程
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(
() -> {
try{
System.out.println("current thread is : "+ Thread.currentThread().getName());
Thread.sleep(
2000
);
}catch (Exception e){
e.printStackTrace();
}
return "success";
}
, executor).exceptionally(e -> {
e.printStackTrace();
return "error";
});
thenCombine
顾名思义,属于组合多个future结果的异步回调方法,观察其中的原理方法,是一个双输入的函数和一个前一阶段返回结果的CompletationStage
public <U,V> CompletableFuture<V> thenCombine( CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends V> fn) { return biApplyStage(null, other, fn); }
BiFunction<? super T,? super U,? extends V> fn
是一个函数式接口,它接收两个参数,一个类型为T
,一个类型为U
,并返回一个类型为V
的结果。
这里的? super T
和? super U
表示参数类型可以是T
和U
的任何父类型,? extends V
表示返回类型可以是V
的任何子类型
可以给出一个这个类型的调用示例:
BiFunction<Integer, Integer, String> biFunction = (num1, num2) -> "Result: " + (num1 + num2);
System.out.println(biFunction.apply(2, 3));
//整合future1和future2的结果,给到future3,并将其打印,可以看到多线程异步结果的组合
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(
() -> {
try{
System.out.println("current thread is : "+ Thread.currentThread().getName());
Thread.sleep(
2000
);
}catch (Exception e){
e.printStackTrace();
}
return " Wang_Zimeng";
}
, executor).exceptionally(e -> {
e.printStackTrace();
return "error";
});
CompletableFuture<String> future3 = future1.thenCombine(future2, (result1, result2) -> {
String str1 = result1 + " "+ result2;
System.out.println(str1 +" "+ Thread.currentThread().getName());
return str1;
});
thenAcceptAsync
CompletableFuture<Void> future3 = future1.thenAcceptBoth(future2, (res1,res2) -> { System.out.println(res1 + res2); });
无返回值,主要用于打印结果
CompletableFuture的应用场景
case 1 执行较为耗时的代码,可以使用异步的CompletableFuture或者FutureTask,如果后继的代码对于当前异步的结果有依赖,可以使用CompletableFuture获取,基于非阻塞式的返回
case2 对大批量数据相同操作的分片异步处理场景
假设我们的应用跑的是城市维度的数据,对于不同的城市希望进行分片异步执行,首先给出分片的代码:
public static <T> List<List<T>> getPartition(List<T> list,int k){
List<List<T>> res = new ArrayList<>();
if(list.size() <= k){
res.add(list);
return res;
}
int count = 0;
while(count < list.size()){
List<T> subList = list.subList(count, Math.min(count+k, list.size()));
res.add(subList);
count += k;
}
return res;
}
然后我们将一维列表通过分片变成二维,进行列表的异步执行,因为每一个分片需要执行一个异步CompletableFuture,所以我们申请futures数组,长度等同于一维数组分片后的partition数量
然后基于这个分组,进行数据的计算(supplyAsync)以及对上一步结果的再处理(thenApplyAsyn)
最终,主线程使用CompletableFuture.allof(futures).join,保证主线程必须在所有future执行完毕后,才可以进行输出展示和打印
List<List<Integer>> res = PartitionUtil.getPartition(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 0), 3);
CompletableFuture[] futures = res.stream().map(partiotionSlice ->
CompletableFuture.supplyAsync(() -> {
try {
//模拟程序调用
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return "success" + partiotionSlice.toString();
}, executor).thenApplyAsync(
(r) -> {
System.out.println(" Wang_Zimeng");
return r + "wangzimeng";
}, executor
)).toArray(CompletableFuture[]::new);
CompletableFuture.allOf(futures).join();
for (CompletableFuture future : futures) {
Object result = future.join();
// 处理结果
System.out.println("CompletableFuturesRes ="+ result);
}
当然,如果是要做数据结果的收集的话,可以使用一个List,作为结果的addAll,最后在joins之后可以保证list的结果是所有获取的futures的终态stage的计算结果