Java并发原理:Callable、Future、FutureTask、CompletableFuture全解
一、Callable、Future、FutureTask简介、特点、优缺点
1.1 三大核心类简介
直接继承Thread或者实现Runnable接口都可以创建线程,但是这两种方法都有一个问题就是:没有返回值,也就是不能获取执行完的结果。因此java1 .5就提供了Callable接口来实现这一场景,而 Future和FutureTask就可以和Callable接口配合起来使用。
1.1.1 Callable<V> 接口
Callable是JDK5新增有返回值、可抛出异常的线程任务接口,替代传统无返回值Runnable接口,位于java.util.concurrent包下。
核心源码:仅一个抽象方法
@FunctionalInterface
public interface Callable<V> {
// 执行任务,返回泛型结果,允许抛出受检异常
V call() throws Exception;
}
1.1.2 Future<V> 接口
Future是JDK5新增异步任务结果管控接口,用于管控Callable/Runnable异步任务,可中断任务、获取异步执行结果、判断任务执行状态,充当异步任务的「结果凭证」。
1.1.3 FutureTask 实现类
FutureTask是Future接口唯一实现类,同时实现Runnable、Future双接口,是线程池、Thread执行异步任务的载体包装类,也是Callable接入线程执行的唯一中转类。
继承关系:Runnable ---> FutureTask <--- Future
1.2 三者核心特点
-
Runnable:无返回值、不能抛受检异常、仅执行业务逻辑;
-
Callable:有泛型返回值、可抛出Exception受检异常、函数式接口;
-
Future:管控异步任务,查询状态、获取结果、取消任务;
-
FutureTask:适配器包装类,承接Callable,转为可被线程执行的任务,缓存执行结果。
1.3 优缺点汇总
1.3.1 优点
-
弥补Runnable线程无返回值短板,支持异步结果返回;
-
支持任务主动取消、状态判断、超时获取结果;
-
FutureTask结果内置缓存,任务执行完毕后多次get仅读取缓存,不会重复执行;
-
适配Thread原生线程、ThreadPoolExecutor线程池双执行方式。
1.3.2 缺点
-
结果获取阻塞式:get()方法拿结果会阻塞主线程;
-
无回调机制:无法任务完成后自动执行后续业务,只能主动轮询/阻塞获取;
-
多级异步任务嵌套代码臃肿,产生回调地狱;
-
异常捕获繁琐,外部无法感知异步任务内部异常;
-
无法灵活编排多个异步任务依赖关系。
二、Future核心API、FutureTask使用、Future局限性
2.1 Future五大核心API详解
|
API方法 |
作用 |
特点 |
|---|---|---|
|
boolean cancel(boolean mayInterruptIfRunning) |
取消正在执行的异步任务 |
true中断运行中任务;false仅取消未执行任务 |
|
boolean isCancelled() |
判断任务是否执行前取消成功 |
执行完毕后返回固定false |
|
boolean isDone() |
判断任务是否完成(正常结束/异常/取消都算完成) |
仅做状态判断,非成功执行判断 |
|
V get() throws InterruptedException,ExecutionException |
阻塞获取任务执行结果 |
无限阻塞,直至拿到结果,抛出两种异常 |
|
V get(long timeout,TimeUnit unit) |
限时阻塞获取结果 |
超时直接抛出TimeoutException,避免永久阻塞 |
2.2 FutureTask三种标准使用方式
Future实际采用FutureTask实现,该对象相当于是消费者和生产者的桥梁,消费者通过FutureTask存储任务的处理结果,更新任务的状态:未开始、正在处理、已完成等。而生产者拿到的FutureTask被转型为Future 接口,可以阻塞式获取任务的处理结果,非阻塞式获取任务处理状态。
FutureTask既可以被当做Runnable来执行,也可以被当做Future来获取Callable的返回结果。
把Callable 实例当作FutureTask 构造函数的参数,生成FutureTask 的对象,然后把这个对象当作一个Runnable 对象,放到线程池中或另起线程去执行,最后还可以通过FutureTask 获取任务执行的结果。
方式1:原生Thread线程执行FutureTask
public class FutureTaskDemo {
public static void main(String[] args) throws Exception {
// 1.定义Callable任务
Callable<Integer> call = () -> {
TimeUnit.SECONDS.sleep(2);
return 100 + 200;
};
// 2.包装为FutureTask
FutureTask<Integer> task = new FutureTask<>(call);
// 3.开启线程执行
new Thread(task).start();
// 4.阻塞获取结果
System.out.println(task.get());
}
}
方式2:线程池execute执行FutureTask
ExecutorService pool = Executors.newFixedThreadPool(2);
FutureTask<String> task = new FutureTask<>(()->{return "线程池执行结果";});
pool.execute(task);
System.out.println(task.get());
方式3:线程池submit直接返回Future
ExecutorService pool = Executors.newFixedThreadPool(2);
// 线程池自动封装FutureTask,直接返回Future
Future<Integer> future = pool.submit(()-> 666);
System.out.println(future.get());
2.3 Future核心局限性
-
结果获取阻塞:get()永久阻塞主线程,轮询isDone()消耗CPU资源;
-
无回调函数:任务执行完毕无法自动通知主线程,无法自动执行后置业务;
-
任务编排能力极差:无法实现任务依赖、聚合、或执行、串行联动;
-
多任务聚合繁琐:批量异步任务,需要手动遍历Future集合获取结果;
-
异常处理被动:异步内部异常只能在get()调用时抛出,无法提前捕获处理;
-
无法链式编程:多个异步步骤串行执行,代码层级嵌套极深。
三、CompletableFuture使用详解(JDK8新增,Future升级版)
CompletableFuture实现Future、CompletionStage双接口,补齐Future全部短板,支持异步回调、链式编排、任务依赖、聚合、异常自动感知,是目前生产异步编程唯一主流方案。
3.1 CompletableFuture七大应用场景
3.1.1 描述依赖关系(串行执行)
任务A执行完毕,再执行任务B,B依赖A的返回结果,串行联动,替代嵌套Future编码。
1.thenApply() 把前面异步任务的结果,交给后面的Function
2.thenCompose()用来连接两个有依赖关系的任务,结果由第二个任务返回
3.1.2 描述AND聚合关系(与关系)
多个异步任务全部执行完成,才触发后续任务,多任务协同后置执行。
1.thenCombine:任务合并,有返回值
2.thenAccepetBoth:两个任务执行完成后,将结果交给thenAccepetBoth消耗,无返回值。
3.runAfterBoth:两个任务都执行完成后,执行下一步操作(Runnable)。
3.1.3 描述OR聚合关系(或关系)
多个异步任务任意一个执行完成,直接触发后续任务,择优执行、快速响应。
1.applyToEither:两个任务谁执行的快,就使用那一个结果,有返回值。
2.acceptEither: 两个任务谁执行的快,就消耗那一个结果,无返回值。
3.runAfterEither: 任意一个任务执行完成,进行下一步操作(Runnable)。
3.1.4 并行执行任务
无依赖多任务并发执行,提升接口吞吐量,压缩整体接口耗时。
CompletableFuture类自己也提供了anyOf()和allOf()用于支持多个CompletableFuture并行执行
补充三大业务场景
-
接口异步拆分、微服务并行调用
-
异步结果回调、异常兜底降级
-
定时异步业务、解耦主线程业务
3.2 创建异步操作四大方法、优缺点、对比图
四类创建方法分类
分为无返回值、有返回值、自定义线程池、默认线程池四大创建方式
// 1.无返回值异步任务(默认ForkJoinPool)
public static CompletableFuture<Void> runAsync(Runnable runnable)
// 2.无返回值异步任务(自定义线程池)
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor)
// 3.有返回值异步任务(默认ForkJoinPool)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
// 4.有返回值异步任务(自定义线程池)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)
四大创建方式对比表
|
创建方法 |
返回值 |
线程来源 |
优点 |
缺点 |
使用场景 |
|---|---|---|---|---|---|
|
runAsync(无池) |
Void无结果 |
公共ForkJoinPool |
使用便捷、无需手动创建线程池 |
共用公共线程、业务互相抢占、IO密集型易阻塞 |
轻量无感异步、日志打印 |
|
runAsync(自定义池) |
Void无结果 |
业务专属线程池 |
线程隔离、可控并发、不占用公共池 |
需要手动初始化线程池 |
耗时无返回异步业务 |
|
supplyAsync(无池) |
有泛型返回值 |
公共ForkJoinPool |
快速获取异步结果、编码极简 |
公共池阻塞风险、线程不可管控 |
小型查询异步任务 |
|
supplyAsync(自定义池) |
有泛型返回值 |
业务专属线程池 |
隔离度最高、生产稳定、可配置参数 |
编码繁琐、需要池托管 |
生产核心异步接口、微服务调用 |
生产强制规范:业务代码禁止使用默认ForkJoinPool,必须传入自定义ThreadPoolExecutor,避免公共线程耗尽导致全局异步失效
3.3 获取异步结果:join() & get() 详解
3.3.1 两大获取方法区别
-
get():继承Future方法,受检异常必须手动try-catch/throws,阻塞获取结果;
-
join():CompletableFuture独有,仅抛出运行时异常,无需强制捕获,编码更简洁。
核心共性:都会阻塞主线程
3.4 结果处理方式、优缺点、对比图
当CompletableFuture的计算结果完成,或者抛出异常的时候,我们可以执行特定的Action。主要是下面的方法:
3.4.1 四大结果处理方式
-
主动阻塞获取:get() / join()
-
回调式处理:whenComplete()
总体可分为以下几种回调方式:
public CompletableFuture <T > whenComplete (BiConsumer<? super T ,? super Throwable >
action)
public CompletableFuture <T > whenCompleteAsync (BiConsumer<? super T ,? super Throwable >
action)
public CompletableFuture <T > whenCompleteAsync (BiConsumer<? super T ,? super Throwable >
action , Executor executor)
-
异常兜底处理:exceptionally()
-
最终收尾处理:handle()
结果处理方式全维度对比表
|
方法 |
是否阻塞 |
是否感知异常 |
是否可兜底返回值 |
优点 |
缺点 |
适用场景 |
|---|---|---|---|---|---|---|
|
get/join |
是 |
是 |
否 |
代码直观、结果即时获取 |
阻塞主线程、影响吞吐量 |
必须同步拿到结果的业务 |
|
whenComplete |
否 |
是 |
否 |
异步回调、不阻塞主线程、同时拿到结果+异常 |
无法修改返回结果、不能降级兜底 |
结果日志打印、异常监控上报 |
|
exceptionally |
否 |
仅异常触发 |
是 |
异常专属兜底、自定义降级返回值 |
正常流程无法干预结果 |
接口异常降级、兜底默认数据 |
|
handle |
否 |
是 |
是 |
全能方法:正常/异常均可修改结果,灵活性最高 |
代码逻辑稍复杂 |
全流程结果改造、统一降级处理 |
3.5 结果转换三大核心方法
所谓结果转换,就是将上一段任务的执行结果作为下一阶段任务的入参参与重新计算,产生新的结果。
3.5.1 thenApply 结果转换
介绍:接收一个函数作为参数,使用该函数处理上一个CompletableFuture 调用的结果,并返回一个具有处理结果的Future对象。
分为以下几种调用方法:
public <U> CompletableFuture <U> thenApply (Function<? super T ,? extends U> fn)
public <U> CompletableFuture <U> thenApplyAsync (Function<? super T ,? extends U> fn)
public <U> CompletableFuture <U> thenApplyAsync (Function<? super T ,? extends U> fn ,
Executor executor)
使用示例
// 数字转字符串结果转换
CompletableFuture.supplyAsync(()->100)
.thenApply(num -> "最终数值:"+num)
.join();
优缺点
-
优点:同步执行、上下文共享、编码简单、异常联动传递
-
缺点:转换逻辑串行阻塞、无法开启新异步线程
使用场景:单一结果格式转换、数据封装、字段加工
3.5.2 thenCompose 结果嵌套异步组合
介绍:thenCompose 的参数为一个返回CompletableFuture 实例的函数,该函数的参数是先前计算步骤的结果。专门解决CompletableFuture嵌套问题
分为以下几种调用方法:
public <U> CompletableFuture <U> thenCompose (Function<? super T , ? extends
CompletionStage <U>> fn);
public <U> CompletableFuture <U> thenComposeAsync (Function<? super T , ? extends
CompletionStage <U>> fn) ;
public <U> CompletableFuture <U> thenComposeAsync (Function<? super T , ? extends
CompletionStage <U>> fn , Executor executor) ;
使用示例
// 根据用户ID异步查询用户信息,二次异步查询用户订单
CompletableFuture.supplyAsync(()->1001L)
.thenCompose(userId -> CompletableFuture.supplyAsync(()->getUserOrder(userId)))
.join();
优缺点
-
优点:扁平化异步代码、避免Future嵌套、支持二次异步、线程解耦
-
缺点:多线程上下文隔离、调试难度高于thenApply
使用场景:异步任务依赖嵌套、二次异步调用、微服务连环调用
3.5.3 thenApply VS thenCompose 核心区别对比
|
对比维度 |
thenApply |
thenCompose |
|---|---|---|
|
执行方式 |
同步串行执行 |
开启新异步任务执行 |
|
返回值 |
普通业务泛型对象 |
CompletableFuture嵌套对象 |
|
代码结构 |
扁平化,无嵌套 |
解决多层Future嵌套回调地狱 |
|
线程 |
复用原有异步线程 |
抢占新线程执行 |
|
选型结论 |
单纯数据转换使用 |
依赖结果开启新异步使用 |
3.6 结果消费四大方法(无返回值)
3.6.1 消费方式总述
与结果处理和结果转换系列函数返回一个新的CompletableFuture 不同,结果消费系列函数只对结果执行Action,而不返回新的计算值。
根据对结果的处理方式,结果消费函数又分为:
- thenAccept系列:对单个结果进行消费
- thenAcceptBoth系列:对两个结果进行消费
- thenRun系列:不关心结果,只对结果执行Action
3.6.2 thenAccept 单结果消费
介绍:接收当前任务结果,消费处理,无返回值
优缺点:编码简单、同步消费;无法联动第二个任务结果、无返回值
使用场景:结果入库、日志打印、消息推送
调用方式及示例代码如下:
#调用方式
public CompletionStage<Void> thenAccept(Consumer<? super T> action);
public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action);
public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action,Executor executor);
#示例代码
CompletableFuture<Void> future = CompletableFuture
.supplyAsync(() -> {
int number = new Random().nextInt(10);
System.out.println("第一阶段:" + number);
return number;
}).thenAccept(number ->
System.out.println("第二阶段:" + number * 5));
System.out.println("最终结果:" + future.get());
3.6.3 thenAcceptBoth 双任务结果消费
介绍:等待两个异步任务全部完成,同时消费两个任务结果,AND关系消费
优缺点:天然聚合双任务结果、线程安全;必须等待两个任务完成、耗时取最大值
使用场景:用户信息+用户积分双结果合并消费
调用方式及示例代码如下:
#调用方式:
public <U> CompletionStage<Void> thenAcceptBoth(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action);
public <U> CompletionStage<Void> thenAcceptBothAsync(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action);
public <U> CompletionStage<Void> thenAcceptBothAsync(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action, Executor executor);
#示例代码
CompletableFuture<Integer> futrue1 = CompletableFuture.supplyAsync(new Supplier<Integer>() {
@Override
public Integer get() {
int number = new Random().nextInt(3) + 1;
try {
TimeUnit.SECONDS.sleep(number);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("第一阶段:" + number);
return number;
}
});
CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(new Supplier<Integer>() {
@Override
public Integer get() {
int number = new Random().nextInt(3) + 1;
try {
TimeUnit.SECONDS.sleep(number);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("第二阶段:" + number);
return number;
}
});
futrue1.thenAcceptBoth(future2, new BiConsumer<Integer, Integer>() {
@Override
public void accept(Integer x, Integer y) {
System.out.println("最终结果:" + (x + y));
}
}).join();
3.6.4 thenRun 无结果消费
介绍:
thenRun 也是对线程任务结果的一种消费函数,与thenAccept不同的是,thenRun 会在上一阶段CompletableFuture 计算完成的时候执行一个Runnable ,Runnable并不使用该CompletableFuture 计算的结果。
优缺点:不依赖结果、仅做后置动作;无法获取前置执行数据
使用场景:任务结束开关变更、链路埋点、资源关闭
调用方式及示例代码如下:
#调用方式:
public CompletionStage<Void> thenRun(Runnable action);
public CompletionStage<Void> thenRunAsync(Runnable action);
public CompletionStage<Void> thenRunAsync(Runnable action,Executor executor);
#示例代码
CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> {
int number = new Random().nextInt(10);
System.out.println("第一阶段:" + number);
return number;
}).thenRun(() ->
System.out.println("thenRun 执行"));
System.out.println("最终结果:" + future.get());
3.7 结果组合:thenCombine 双任务结果合并
3.7.1 thenCombine介绍使用
介绍:等待两个独立异步任务执行完毕,获取两个结果,合并加工返回新结果,AND合并关系
// 合并用户姓名+用户年龄,组装用户信息
CompletableFuture<String> nameFuture = CompletableFuture.supplyAsync(()->"李四");
CompletableFuture<Integer> ageFuture = CompletableFuture.supplyAsync(()->22);
nameFuture.thenCombine(ageFuture,(name,age)-> name+":"+age).join();
优缺点
-
优点:双任务并行执行、互不阻塞、结果合并灵活、耗时取任务最大值
-
缺点:任一任务异常,整体合并直接失败
使用场景:多微服务并行调用、多维度数据组装、聚合接口开发
3.8 任务交互六大方法(OR/AND择优、批量任务)
所谓线程交互,是指将两个线程任务获取结果的速度相比较,按一定的规则进行下一步处理。
3.8.1 applyToEither 择优转换
介绍:两个任务任意一个先执行完成,用完成结果做转换,有返回值
优缺点:快速响应、降级择优;慢任务结果直接丢弃
使用场景:接口双机房调用、择优最快结果返回
示例代码:
// 一、applyToEither 系列方法定义
# applyToEither:两个任务任意一个先完成,拿到最先完成任务的返回值,执行Function转换并返回新结果
# applyToEitherAsync:异步执行后续转换逻辑,使用默认线程池
# applyToEitherAsync(..., Executor):异步执行并指定自定义线程池
public <U> CompletionStage<U> applyToEither(CompletionStage<? extends T> other,Function<? super T, U> fn);
public <U> CompletionStage<U> applyToEitherAsync(CompletionStage<? extends T> other,Function<? super T, U> fn);
public <U> CompletionStage<U> applyToEitherAsync(CompletionStage<? extends T> other,Function<? super T, U> fn,Executor executor);
// 二、完整代码示例
# 1. 创建异步任务future1:随机0-9数字,睡眠对应秒数后输出并返回数字
CompletableFuture<Integer> future1 = CompletableFuture
.supplyAsync(new Supplier<Integer>() {
@Override
public Integer get() {
int number = new Random().nextInt(10);
System.out.println("第一阶段start:" + number);
try {
TimeUnit.SECONDS.sleep(number);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("第一阶段end:" + number);
return number;
}
});
# 2. 创建异步任务future2:逻辑同future1,独立随机耗时
CompletableFuture<Integer> future2 = CompletableFuture
.supplyAsync(new Supplier<Integer>() {
@Override
public Integer get() {
int number = new Random().nextInt(10);
System.out.println("第二阶段start:" + number);
try {
TimeUnit.SECONDS.sleep(number);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("第二阶段end:" + number);
return number;
}
});
# 3. applyToEither合并两个任务:谁先执行完就取谁的结果,将结果*2返回
future1.applyToEither(future2, new Function<Integer, Integer>() {
@Override
public Integer apply(Integer number) {
System.out.println("最快结果:" + number);
return number * 2;
}
});
3.8.2 acceptEither 择优消费
介绍:任意任务完成,消费最快结果,无返回值
优缺点:节省等待耗时;无法兜底慢任务异常
使用场景:双数据源择优消费、消息优先处理
示例代码如下:
# acceptEither 系列方法说明:两个任务任意一个先完成,消费最先完成任务的返回值,无返回结果
# acceptEither:同步执行消费逻辑
# acceptEitherAsync:异步执行消费逻辑,使用默认线程池
# acceptEitherAsync(..., Executor):异步执行消费逻辑,自定义线程池
public CompletionStage<Void> acceptEither(CompletionStage<? extends T> other,Consumer<? super T> action);
public CompletionStage<Void> acceptEitherAsync(CompletionStage<? extends T> other,Consumer<? super T> action);
public CompletionStage<Void> acceptEitherAsync(CompletionStage<? extends T> other,Consumer<? super T> action,Executor executor);
# 创建异步任务future1,随机1~10数字,睡眠对应秒数后输出并返回数字
CompletableFuture<Integer> future1 = CompletableFuture
.supplyAsync(new Supplier<Integer>() {
@Override
public Integer get() {
int number = new Random().nextInt(10) + 1;
try {
TimeUnit.SECONDS.sleep(number);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("第一阶段:" + number);
return number;
}
});
# 创建异步任务future2,逻辑同future1,独立随机耗时
CompletableFuture<Integer> future2 = CompletableFuture
.supplyAsync(new Supplier<Integer>() {
@Override
public Integer get() {
int number = new Random().nextInt(10) + 1;
try {
TimeUnit.SECONDS.sleep(number);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("第二阶段:" + number);
return number;
}
});
# acceptEither:两个任务谁先完成就消费谁的结果,仅打印不返回值,join阻塞等待流程全部结束
future1.acceptEither(future2, new Consumer<Integer>() {
@Override
public void accept(Integer number) {
System.out.println("最快结果:" + number);
}
}).join();
3.8.3 runAfterEither 任一完成后置执行
介绍:两个任务任意完成,执行后置无参动作,不获取结果
优缺点:响应速度快;不感知执行结果
使用场景:任一任务结束后关闭连接、修改状态
示例代码如下:
# runAfterEither 系列方法说明:两个任务任意一个执行完成后,执行Runnable无参无返回回调,不接收任务返回值
# runAfterEither:同步执行后续Runnable逻辑
# runAfterEitherAsync:异步执行Runnable,使用ForkJoin默认线程池
# runAfterEitherAsync(..., Executor):异步执行Runnable,指定自定义线程池
public CompletionStage<Void> runAfterEither(CompletionStage<?> other,Runnable action);
public CompletionStage<Void> runAfterEitherAsync(CompletionStage<?> other,Runnable action);
public CompletionStage<Void> runAfterEitherAsync(CompletionStage<?> other,Runnable action,Executor executor);
# 创建异步任务future1,随机0~4数字,睡眠对应秒数,输出日志并返回数字
CompletableFuture<Integer> future1 = CompletableFuture
.supplyAsync(new Supplier<Integer>() {
@Override
public Integer get() {
int number = new Random().nextInt(5);
try {
TimeUnit.SECONDS.sleep(number);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("第一阶段:" + number);
return number;
}
});
# 创建异步任务future2,逻辑同future1,独立随机耗时
CompletableFuture<Integer> future2 = CompletableFuture
.supplyAsync(new Supplier<Integer>() {
@Override
public Integer get() {
int number = new Random().nextInt(5);
try {
TimeUnit.SECONDS.sleep(number);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("第二阶段:" + number);
return number;
}
});
# runAfterEither:任一任务完成就执行Runnable,无法获取任务返回值,join阻塞等待整体流程结束
future1.runAfterEither(future2, new Runnable() {
@Override
public void run() {
System.out.println("已经有一个任务完成了");
}
}).join();
3.8.4 runAfterBoth 全部完成后置执行
介绍:两个任务全部执行完毕,执行后置动作,AND后置执行
优缺点:保证双任务执行完毕;耗时取最长任务时长
使用场景:双业务完成后统一归档、统一清理资源
示例代码如下:
# runAfterBoth 系列方法说明:必须等待两个任务全部执行完成后,才执行Runnable回调,不接收两个任务的返回结果,无返回值
# runAfterBoth:同步执行后续Runnable逻辑
# runAfterBothAsync:异步执行Runnable,使用默认ForkJoin线程池
# runAfterBothAsync(..., Executor):异步执行Runnable,支持传入自定义线程池
public CompletionStage<Void> runAfterBoth(CompletionStage<?> other,Runnable action);
public CompletionStage<Void> runAfterBothAsync(CompletionStage<?> other,Runnable action);
public CompletionStage<Void> runAfterBothAsync(CompletionStage<?> other,Runnable action,Executor executor);
# 创建异步任务future1,固定休眠1秒,打印日志并返回数字1
CompletableFuture<Integer> future1 = CompletableFuture
.supplyAsync(new Supplier<Integer>() {
@Override
public Integer get() {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("第一阶段:1");
return 1;
}
});
# 创建异步任务future2,固定休眠2秒,打印日志并返回数字2
CompletableFuture<Integer> future2 = CompletableFuture
.supplyAsync(new Supplier<Integer>() {
@Override
public Integer get() {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("第二阶段:2");
return 2;
}
});
# runAfterBoth:等待future1、future2两个任务全部结束后执行回调,无法获取两个任务返回值,get阻塞等待全部流程执行完毕
future1.runAfterBoth(future2, new Runnable() {
@Override
public void run() {
System.out.println("上面两个任务都执行完成了。");
}
}).get();
3.8.5 anyOf 批量任一完成
介绍:传入多个CompletableFuture数组,任意一个执行完成,直接返回结果
优缺点:批量择优、适配多任务;优先返回完成结果,不保证业务合规性
使用场景:多节点健康探测、多接口抢最快响应
示例代码如下:
# anyOf 静态方法说明:传入多个CompletableFuture数组,只要其中任意一个任务先执行完成,立刻返回该任务结果,其余任务仍会继续后台运行
public static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs)
# 创建随机数对象,用于生成任务休眠时长
Random random = new Random();
# 异步任务future1:随机休眠0~4秒,完成后返回字符串hello
CompletableFuture<String> future1 = CompletableFuture
.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(random.nextInt(5));
} catch (InterruptedException e) {
e.printStackTrace();
}
return "hello";
});
# 异步任务future2:随机休眠0秒,完成后返回字符串world
CompletableFuture<String> future2 = CompletableFuture
.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(random.nextInt(1));
} catch (InterruptedException e) {
e.printStackTrace();
}
return "world";
});
# anyOf接收多个任务,取最先完成的任务结果,get阻塞等待首个任务完成
CompletableFuture<Object> result = CompletableFuture.anyOf(future1, future2);
System.out.println(result.get());
3.8.6 allOf 批量全部完成
介绍:传入Future数组,所有异步任务全部执行完毕,才算任务完成
优缺点:批量管控、统一收尾;任一任务异常整体失败、耗时取最大值
使用场景:批量异步新增、批量文件处理、全量数据同步
示例代码如下:
# allOf 静态方法说明:接收多个CompletableFuture可变参数,必须等待所有任务全部执行完毕才会完成;返回CompletableFuture<Void>,无法直接获取各个任务返回值,需单独调用每个future.get()拿结果
public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs)
# 异步任务future1:休眠2秒后打印日志并返回字符串
CompletableFuture<String> future1 = CompletableFuture
.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("future1完成!");
return "future1完成!";
});
# 异步任务future2:无休眠,直接打印日志并返回字符串
CompletableFuture<String> future2 = CompletableFuture
.supplyAsync(() -> {
System.out.println("future2完成!");
return "future2完成!";
});
# 使用allOf组合两个任务,等待全部任务执行结束
CompletableFuture<Void> combindFuture = CompletableFuture
.allOf(future1, future2);
# get阻塞等待所有任务完成,捕获中断、执行异常
try {
combindFuture.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
# 打印两个任务是否全部执行完成
System.out.println("future1: " + future1.isDone() + ",future2: " + future2.isDone());
更多推荐



所有评论(0)