CompletableFuture类的作用
业务场景
一个接口可能需要调用 N 个其他服务的接口,这在项目开发中还是挺常见的。举个例子:用户请求获取订单信息,可能需要调用用户信息、商品详情、物流信息、商品推荐等接口,最后再汇总数据统一返回。
如果是串行(按顺序依次执行每个任务)执行的话,接口的响应速度会非常慢。考虑到这些接口之间有大部分都是 无前后顺序关联 的,可以 并行执行 ,就比如说调用获取商品详情的时候,可以同时调用获取物流信息。通过并行执行多个任务的方式,接口的响应速度会得到大幅优化。
Future
在实际使用过程中存在一些局限性比如不支持异步任务的编排组合、获取计算结果的 get()
方法为阻塞调用。
Java 8 引入CompletableFuture
类可以解决Future
的这些缺陷。CompletableFuture
除了提供了更为好用和强大的 Future
特性之外,还提供了函数式编程、异步任务编排组合(可以将多个异步任务串联起来,组成一个完整的链式调用)等能力。
异步执行一个任务时,一般是用线程池Executor
去创建。如果不需要有返回值,任务实现Runnable
接口;如果需要有返回值,任务实现Callable
接口,调用Executor
的submit
方法提交任务,再使用Future
获取结果即可。
如果多个线程存在依赖组合的话,怎么处理呢?
- 可使用同步组件CountDownLatch、CyclicBarrier等,但是比较麻烦。
- 简单一点的方法,就是用CompletableFuture类。
对于存在前后顺序关系的接口调用,可以进行编排,如下图所示。图源JavaGuide面试题

- 获取用户信息之后,才能调用商品详情和物流信息接口。
- 成功获取商品详情和物流信息之后,才能调用商品推荐接口。
对于 Java 程序来说,Java 8 才被引入的 CompletableFuture
可以帮助我们来做多个任务的编排,功能非常强大。
使用Future接口异步执行任务
java
//依赖的MedalInfo和UserInfo不放上来了
public class MedalService {
public MedalInfo getMedalInfo(Long userId) {
try {
Thread.sleep(200); // 模拟调用耗时
} catch (InterruptedException e) {
e.printStackTrace();
}
return new MedalInfo("666", "守护勋章");
}
}
public class UserInfoService {
public UserInfo getUserInfo(Long userId) {
try {
Thread.sleep(100); // 模拟调用耗时
} catch (InterruptedException e) {
e.printStackTrace();
}
return new UserInfo("666", "ET", 27); //一般是查数据,或者rpc调用返回
}
}
import java.util.concurrent.*;
public class FutureAsync {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService executorService = Executors.newFixedThreadPool(10);
UserInfoService userInfoService = new UserInfoService();
MedalService medalService = new MedalService();
long userId = 666L;
long startTime = System.currentTimeMillis();
// 调用用户服务获取用户基本信息
FutureTask<UserInfo> userInfoFutureTask = new FutureTask<>(new Callable<UserInfo>(){
@Override
public UserInfo call() throws Exception {
return userInfoService.getUserInfo(userId);
}
});
executorService.submit(userInfoFutureTask);
//模拟主线程其他操作
Thread.sleep(300);
FutureTask<MedalInfo> medalInfoFutureTask = new FutureTask<>(new Callable<MedalInfo>() {
@Override
public MedalInfo call() throws Exception {
return medalService.getMedalInfo(userId);
}
});
//获取个人信息的结果
UserInfo userInfo = userInfoFutureTask.get();
//获取勋章信息结果
MedalInfo medalInfo = medalInfoFutureTask.get();
System.out.println("总共用时" + (System.currentTimeMillis() - startTime) + "ms");
}
}
如果我们不使用Future进行并行异步调用,而是在主线程串行进行的话,耗时大约为300+500+300 = 1100 ms。可以发现,future+线程池异步配合,提高了程序的执行效率。
但是Future对于结果的获取,不是很友好,只能通过阻塞 或者轮询的方式得到任务的结果。
- Future.get() 就是阻塞调用,在线程获取结果之前get方法会一直阻塞。
- Future提供了一个isDone方法,可以在程序中轮询这个方法查询执行结果。
阻塞的方式和异步编程的设计理念相违背,而轮询的方式会耗费无谓的CPU资源。因此,JDK8设计出CompletableFuture。CompletableFuture提供了一种观察者模式类似的机制,可以让任务执行完成后通知监听的一方。
使用CompletableFuture改造上述代码
java
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
public class CompletableFutureAsync {
public static void main(String[] args) throws InterruptedException, TimeoutException, ExecutionException {
UserInfoService userInfoService = new UserInfoService();
MedalService medalService = new MedalService();
long userId = 666L;
long startTime = System.currentTimeMillis();
CompletableFuture<UserInfo> userInfoCompletableFuture =
CompletableFuture.supplyAsync(() -> userInfoService.getUserInfo(userId));
Thread.sleep(20);
CompletableFuture<MedalInfo> medalInfoCompletableFuture =
CompletableFuture.supplyAsync(() -> medalService.getMedalInfo(userId));
UserInfo userInfo = userInfoCompletableFuture.get(2, TimeUnit.SECONDS);
MedalInfo medalInfo = medalInfoCompletableFuture.get();
System.out.println("总共用时" + (System.currentTimeMillis() - startTime) + "ms");
}
}
CompletableFuture的supplyAsync方法,提供了异步执行(是指几个任务不在主线程里按顺序执行 )的功能,线程池也不用单独创建了。实际上,它使用了默认线程池是ForkJoinPool.commonPool。
CompletableFuture的使用
创建CompletableFuture对象
有两种方式
- 通过 new 关键字。
- 基于
CompletableFuture
自带的静态工厂方法:runAsync()
、supplyAsync()
。
new方式创建CompletableFuture对象
用CompletableFuture来存放RpcResponse类型的结果
java
CompletableFuture<RpcResponse<Object>> resultFuture = new CompletableFuture<RpcResponse<Object>>();
如果后续得到结果rpcResponse,可以调用complete方法,将其传入CompletableFuture容器。
complete只能调用一次,后续调用都是无效的
java
resultFuture.complete(rpcResponse);
获取异步运算的结果
java
rpcResponse = resultFuture.get();
如果已经知道运算结果的话,可以使用使用静态方法 completedFuture()
来创建 CompletableFuture
java
CompletableFuture<String> future = CompletableFuture.completedFuture("hello!");
assertEquals("hello!", future.get());
静态工厂方法:runAsync()
、supplyAsync()
源码
java
static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier);
// 使用自定义线程池(推荐)
static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor);
static CompletableFuture<Void> runAsync(Runnable runnable);
// 使用自定义线程池(推荐)
static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor);
runAsync()
方法接受的参数是Runnable
,这是一个函数式接口,不允许返回值。当你需要异步操作且不关心返回结果的时候可以使用runAsync()
方法。supplyAsync()
方法接受的参数是Supplier<U>
,这也是一个函数式接口,U
是返回结果值的类型。当你需要异步操作且关心返回结果的时候,可以使用supplyAsync()
方法。
举例
最简单的例子
java
public class SupplyAsync {
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "hello supply");
System.out.println(future.get());
CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> System.out.println("hello run"));
System.out.println(future1.get());
}
}
执行结果
arduino
hello supply
hello run
null
额外话题:这里可以再学习下lambda表达式,runnable接口无返回值,lambda表达式就不能这么写成有返回值的形式。

任务异步回调
有以下几个异步回调方法
thenApply/thenApplyAsync
这个方法可以从上一个任务中获取结果,并进行处理之后返回
源码
java
//沿用上一个任务的线程池
public <U> CompletableFuture<U> thenApply(
Function<? super T,? extends U> fn) {
return uniApplyStage(null, fn);
}
// 使用默认的`ForkJoinPool`线程池(不推荐使用)
public <U> CompletableFuture<U> thenApplyAsync(
Function<? super T,? extends U> fn) {
return uniApplyStage(asyncPool, fn);
}
// 使用自定义线程池
public <U> CompletableFuture<U> thenApplyAsync(
Function<? super T,? extends U> fn, Executor executor) {
return uniApplyStage(screenExecutor(executor), fn);
}
使用案例
kotlin
public class ThenApplyMethod {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 1
CompletableFuture<String> orgFuture = CompletableFuture.supplyAsync(() -> {
System.out.println("原始CompletableFuture方法");
return "ET的掘金社区";
});
System.out.println(orgFuture.get());
// 2
CompletableFuture<String> thenApplyFuturePrint = orgFuture.thenApply((a) -> {
return "123";
});
System.out.println(thenApplyFuturePrint.get());
// 3
CompletableFuture<String> thenApplyFuture = orgFuture.thenApply((a) -> {
if ("ET的掘金社区".equals(a)) {
return "关注了";
}
return "先考虑考虑";
});
System.out.println(thenApplyFuture.get());
// 4
CompletableFuture<String> future = CompletableFuture.completedFuture("hello!").thenApply(s -> s + "world!");
System.out.println(future.get());
// 5
future.thenApply(s -> s + "nice!");
System.out.println(future.get());
}
}
代码理解如下:
- 第1段代码:先将
ET的掘金社区
这个对象放到orgFuture,再通过get()
方法获取该对象 - 第2段代码:没有用到
orgFuture
的返回值进行处理 - 第3段代码:根据
orgFuture
中的值进行后续处理 - 第4段代码:调用
completedFuture
直接将结果放到future
,再调用thenApply
处理 - 第5段代码:再次调用
thenApply
的操作不生效 - 第6段代码:直接流式调用,两次调用
thenApply
是都生效的。
执行结果
bash
原始CompletableFuture方法
ET的掘金社区
123
关注了
hello!world!
hello!world!
hello!world!nice!
如果业务中不需要从回调函数(这几个异步回调方法)中获取返回结果,可以使用thenAccept()
或者thenRun()
。这两个方法的区别在于thenRun()
不能访问异步计算的结果。
thenAccept/thenAccepAsync
可以获取上一步的执行结果之后进行处理,但是该方法没有返回值,无法返回处理结果
源码
java
public CompletableFuture<Void> thenAccept(Consumer<? super T> action) {
return uniAcceptStage(null, action);
}
public CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action) {
return uniAcceptStage(asyncPool, action);
}
public CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action,
Executor executor) {
return uniAcceptStage(screenExecutor(executor), action);
}
使用案例
java
public class ThenAcceptMethod {
public static void thenAccept() throws ExecutionException, InterruptedException {
CompletableFuture<String> orgFuture = CompletableFuture.supplyAsync(() -> {
System.out.println("原始的CompletableFuture方法");
return "ET的掘金社区";
});
CompletableFuture thenAcceptFuture = orgFuture.thenAccept((a) -> {
if ("ET的掘金社区".equals(a)) {
System.out.println("关注了");
}else
System.out.println("先考虑考虑");
});
System.out.println(thenAcceptFuture.get());
}
public static void thenAccept2() {
CompletableFuture.completedFuture("hello!").thenApply(s -> s + "world!").thenApply(s -> s + "nice!").thenAccept((a) -> {
if ("hello!world!nice!".equals(a)) {
System.out.println("获取到了");
}
});
// 会报错,因为thenAccept传入的是Consumer(lambda表达式还没理解透彻)
CompletableFuture.completedFuture("hello!").thenApply(s -> s + "world!").thenAccept(s -> s + "nice!");
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
ThenAcceptMethod.thenAccept();
ThenAcceptMethod.thenAccept2();
}
}
执行结果
csharp
原始的CompletableFuture方法
关注了
null
获取到了
thenRun/thenRunAsync
源码
java
public CompletableFuture<Void> thenRun(Runnable action) {
return uniRunStage(null, action);
}
public CompletionStage<Void> thenRunAsync(Runnable action);
public CompletionStage<Void> thenRunAsync(Runnable action, Executor executor);
CompletableFuture的thenRun方法,通俗点讲就是,做完第一个任务后,再做第二个任务 。某个任务执行完成后,执行回调方法;但是前后两个任务没有参数传递,thenRun执行之后也没有返回值
使用案例
java
public class ThenRunMethod {
public static void thenRun() {
CompletableFuture<String> orgFuture = CompletableFuture.supplyAsync(() -> {
System.out.println("原始的ComletableFuture方法");
return "ET的掘金社区";
});
CompletableFuture thenRunFuture = orgFuture.thenRun(() -> {
System.out.println("无法获取到orgFuture的上一个结果,直接执行第2个任务");
});
}
public static void thenRun2() {
CompletableFuture.completedFuture("hello!").thenApply(s -> s + "world!").thenRun(() -> {
System.out.println("hello");
});
}
public static void main(String[] args) {
ThenRunMethod.thenRun();
ThenRunMethod.thenRun2();
}
}
执行结果:
原始的ComletableFuture方法
无法获取到orgFuture的上一个结果,直接执行第2个任务
hello
异常处理
handle方法
源码
java
public <U> CompletableFuture<U> handle(
BiFunction<? super T, Throwable, ? extends U> fn) {
return uniHandleStage(null, fn);
}
public <U> CompletableFuture<U> handleAsync(
BiFunction<? super T, Throwable, ? extends U> fn) {
return uniHandleStage(defaultExecutor(), fn);
}
public <U> CompletableFuture<U> handleAsync(
BiFunction<? super T, Throwable, ? extends U> fn, Executor executor) {
return uniHandleStage(screenExecutor(executor), fn);
}
使用案例
java
public class HandleMethod {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 1
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
if (true) {
throw new RuntimeException("Computation error!");
}
return "hello";
}).handle((res, ex) -> {
return res != null ? res : "world!";
});
System.out.println(future.get());
//2 不处理异常,执行报错
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
if (true) {
throw new RuntimeException("Computation error!");
}
return "hello";
});
System.out.println(future2.get());
}
}
代码解读:
- 第1段:future恒抛出异常,使用handle处理之后再返回res,可以通过get方法正常获取future中的值
- 第2段:future恒抛出异常,没有进行异常处理,代码执行会报错。
执行结果:
java
world!
Exception in thread "main" java.util.concurrent.ExecutionException: java.lang.RuntimeException: Computation error!
at java.util.concurrent.CompletableFuture.reportGet(CompletableFuture.java:357)
at java.util.concurrent.CompletableFuture.get(CompletableFuture.java:1895)
at multithread.completablefuture.differentmethod.HandleMethod.main(HandleMethod.java:24)
Caused by: java.lang.RuntimeException: Computation error!
at multithread.completablefuture.differentmethod.HandleMethod.lambda$main$2(HandleMethod.java:20)
at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1590)
at java.util.concurrent.CompletableFuture$AsyncSupply.exec(CompletableFuture.java:1582)
at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056)
at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)
at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)
exceptionally方法
源码
使用案例
arduino
public class ExceptionallyMethod {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 1
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
if (true) {
throw new RuntimeException("Computation errlr!");
}
return "hello";
}).exceptionally(ex -> {
System.out.println(ex.toString());
return "world";
});
System.out.println(future.get());
// 2
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
if (true) {
throw new RuntimeException("Computation errlr!");
}
return "hello";
});
System.out.println(future2.get());
}
}
执行结果:
java
java.util.concurrent.CompletionException: java.lang.RuntimeException: Computation errlr!
world
Exception in thread "main" java.util.concurrent.ExecutionException: java.lang.RuntimeException: Computation errlr!
at java.util.concurrent.CompletableFuture.reportGet(CompletableFuture.java:357)
at java.util.concurrent.CompletableFuture.get(CompletableFuture.java:1895)
at multithread.completablefuture.differentmethod.ExceptionallyMethod.main(ExceptionallyMethod.java:26)
Caused by: java.lang.RuntimeException: Computation errlr!
at multithread.completablefuture.differentmethod.ExceptionallyMethod.lambda$main$2(ExceptionallyMethod.java:22)
at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1590)
at java.util.concurrent.CompletableFuture$AsyncSupply.exec(CompletableFuture.java:1582)
at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056)
at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)
at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)
completeExceptionally方法
如果你想让 CompletableFuture
的结果就是异常的话,可以使用 completeExceptionally()
方法为其赋值。
dart
CompletableFuture<String> completableFuture = new CompletableFuture<>();
// ...
completableFuture.completeExceptionally(
new RuntimeException("Calculation failed!"));
// ...
completableFuture.get(); // ExecutionException
多个任务组合处理
AND组合的关系
thenCombine
thenCombine / thenAcceptBoth / runAfterBoth都表示:将两个CompletableFuture组合起来,只有这两个都正常执行完了,才会执行某个任务。
区别在于:
- thenCombine:会将两个任务的执行结果作为方法入参,传递到指定方法中,且有返回值
- thenAcceptBoth: 会将两个任务的执行结果作为方法入参,传递到指定方法中,且无返回值
- runAfterBoth 不会把执行结果当做方法入参,且没有返回值。
使用案例
java
public class ThenCombineMethod {
public static void main(String[] args) {
CompletableFuture<String> first = CompletableFuture.completedFuture("第一个异步任务");
ExecutorService executor = Executors.newFixedThreadPool(10);
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "第二个异步任务", executor)
.thenCombineAsync(first, (s, w) -> {
System.out.println(w);
System.out.println(s);
return "两个异步任务的组合";
}, executor);
System.out.println(future.join());
executor.shutdown();
}
}
运行结果
第一个异步任务
第二个异步任务
两个异步任务的组合
thenCompose
thenCompose方法会在某个任务执行完成后,将该任务的执行结果,作为方法入参,去执行指定的方法。该方法会返回一个新的CompletableFuture实例
- 如果该CompletableFuture实例的result不为null,则返回一个基于该result新的CompletableFuture实例;
- 如果该CompletableFuture实例为null,然后就执行这个新任务
java
public class ThenComposeTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture<String> f = CompletableFuture.completedFuture("第一个任务");
//第二个异步任务
ExecutorService executor = Executors.newSingleThreadExecutor();
CompletableFuture<String> future = CompletableFuture
.supplyAsync(() -> "第二个任务", executor)
.thenComposeAsync(data -> {
System.out.println(data); return f; //使用第一个任务作为返回
}, executor);
System.out.println(future.join());
executor.shutdown();
}
}
//输出
第二个任务
第一个任务
OR的关系

applyToEither / acceptEither / runAfterEither 都表示:将两个CompletableFuture组合起来,只要其中一个执行完了,就会执行某个任务。
区别在于:
- applyToEither:会将已经执行完成的任务,作为方法入参,传递到指定方法中,且有返回值
- acceptEither: 会将已经执行完成的任务,作为方法入参,传递到指定方法中,且无返回值
- runAfterEither: 不会把执行结果当做方法入参,且没有返回值。
applyToEither/applyToEitherAsync
源码
java
public <U> CompletableFuture<U> applyToEitherAsync(
CompletionStage<? extends T> other, Function<? super T, U> fn) {
return orApplyStage(asyncPool, other, fn);
}
案例
java
package multithread.completablefuture;
import java.util.List;
import java.util.concurrent.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
public class ApplyToEither {
//获取CPU核数,我电脑执行下来,是8
private final static int AVALIBLE_PROCESSORS = Runtime.getRuntime().availableProcessors();
// 自定义一个线程池
// 核心线程数8,最大线程俗话16,多余线程存活时间5s,
// 存储等待执行的任务队列大小5,拒绝策略:除非executor被关闭,否则任务不会被丢弃。
private final static ThreadPoolExecutor POOL_EXECUTOR = new ThreadPoolExecutor(
AVALIBLE_PROCESSORS,
AVALIBLE_PROCESSORS * 2,
5,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(5),
new ThreadPoolExecutor.CallerRunsPolicy()
);
//定时器线程,专门给fastFail使用
private static final ScheduledExecutorService SCHEDULED_EXECUTOR_SERVICE =
Executors.newScheduledThreadPool(
1,
new BasicThreadFactory.Builder().namingPattern("ScheduleFuture-Thread-%d")
.daemon(true)
.build()
);
//构造一个指定时间后抛异常的future,futureB
private static <T> CompletableFuture<T> fastFail(long timeout, TimeUnit timeUnit) {
final CompletableFuture<T> future = new CompletableFuture<>();
SCHEDULED_EXECUTOR_SERVICE.schedule(() -> {
final TimeoutException ex = new TimeoutException("超时拉..." + timeout);
return future.completeExceptionally(ex);
}, timeout, timeUnit);
return future;
}
// 调用supplyAsync异步执行模拟调用第三方服务的操作,futureA(异步是指,不在主线程里按顺序执行)
// 然后链式调用applyToEitherAsync方法(最快返回输出的线程结果作为下一次任务的输入),这个方法返回futureA和futureB中返回更快的那个,逻辑如下:
// 1、futureA如果在2s之前返回,则该方法会返回futureA的结果,即:serviceName
// 2、futureA如果在2s之后返回,则该方法会futureB的值,也就是超时异常
// 这里方法传入的Function.identity()的返回结果,其实就是返回futureA自身,自己文章里有写这个方法的作用
private static CompletableFuture<String> query3rdService(String serviceName) {
return CompletableFuture.supplyAsync(() -> {
try {
long t = ThreadLocalRandom.current().nextInt(5);
System.out.println(Thread.currentThread().getName() + "请求服务" + serviceName);
TimeUnit.SECONDS.sleep(t);
} catch (InterruptedException e) {
e.printStackTrace();
}
return serviceName;
}, POOL_EXECUTOR).applyToEitherAsync(fastFail(2000, TimeUnit.MILLISECONDS), Function.identity()).exceptionally(e -> "error");
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
//生成6个请求第三方服务的future,即6个线程
List<CompletableFuture<String>> futureList =
IntStream.range(0, 6).mapToObj(i -> query3rdService("Service" + i)).collect(Collectors.toList());
//6个请求并发执行,即:6个线程
futureList.forEach(CompletableFuture::join);
for (CompletableFuture<String> future : futureList) {
System.out.println(future.get());
}
}
}
执行结果和分析
arduino
pool-1-thread-1 请求服务 Service0需要时间0秒
pool-1-thread-2 请求服务 Service1需要时间2秒
pool-1-thread-3 请求服务 Service2需要时间4秒
pool-1-thread-4 请求服务 Service3需要时间3秒
pool-1-thread-6 请求服务 Service5需要时间2秒
pool-1-thread-5 请求服务 Service4需要时间2秒
Service0
Service1
error
error
Service4
Service5
futureA中请求service3和Service5超过2s,则返回futureB的报错:error 否则返回:serviceName,即:Service0-6。
acceptEither / runAfterEither
暂时未找应用场景