Java并发编程:CompletableFuture类

CompletableFuture类的作用

业务场景

一个接口可能需要调用 N 个其他服务的接口,这在项目开发中还是挺常见的。举个例子:用户请求获取订单信息,可能需要调用用户信息、商品详情、物流信息、商品推荐等接口,最后再汇总数据统一返回。

如果是串行(按顺序依次执行每个任务)执行的话,接口的响应速度会非常慢。考虑到这些接口之间有大部分都是 无前后顺序关联 的,可以 并行执行 ,就比如说调用获取商品详情的时候,可以同时调用获取物流信息。通过并行执行多个任务的方式,接口的响应速度会得到大幅优化。

Future 在实际使用过程中存在一些局限性比如不支持异步任务的编排组合、获取计算结果的 get() 方法为阻塞调用。

Java 8 引入CompletableFuture 类可以解决Future 的这些缺陷。CompletableFuture 除了提供了更为好用和强大的 Future 特性之外,还提供了函数式编程、异步任务编排组合(可以将多个异步任务串联起来,组成一个完整的链式调用)等能力。

异步执行一个任务时,一般是用线程池Executor去创建。如果不需要有返回值,任务实现Runnable接口;如果需要有返回值,任务实现Callable接口,调用Executorsubmit方法提交任务,再使用Future获取结果即可。

如果多个线程存在依赖组合的话,怎么处理呢?

  • 可使用同步组件CountDownLatch、CyclicBarrier等,但是比较麻烦。
  • 简单一点的方法,就是用CompletableFuture类。

对于存在前后顺序关系的接口调用,可以进行编排,如下图所示。图源JavaGuide面试题

  1. 获取用户信息之后,才能调用商品详情和物流信息接口。
  2. 成功获取商品详情和物流信息之后,才能调用商品推荐接口。

对于 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对象

有两种方式

  1. 通过 new 关键字。
  2. 基于 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

暂时未找应用场景

参考文章

  1. 异步编程利器:CompletableFuture详解 |Java 开发实战
  2. cloud.tencent.com/developer/a...
  3. juejin.cn/post/708897...
  4. 利用 CompletableFuture的applyToEither 优雅的实现快速失败
相关推荐
白露与泡影1 分钟前
SpringBoot前后端token自动续期方案
spring boot·后端·状态模式
青梅主码9 分钟前
重磅!《人工智能和大型语言模型的研究前景:应用、挑战和未来方向》:代理型 AI 和大语言模型是否可以整合?
后端
hui函数24 分钟前
Flask-WTF表单验证全攻略
后端·python·flask·web·表单验证
喵手28 分钟前
Java异常处理最佳实践:如何避免捕获到不必要的异常?
java·后端·java ee
猿java40 分钟前
精通MySQL却不了解OLAP和 OLTP,正常吗?
java·后端·面试
喵手1 小时前
Java中的HashMap:你了解它的工作原理和最佳实践吗?
java·后端·java ee
冷月半明1 小时前
把离线 Python 项目塞进 Docker:从 0 到 1 的踩坑实录,一口气讲透 10 个最常见困惑
后端
用户298698530141 小时前
如何使用 Spire.Doc 在 Word 中查找和替换文本?
后端
宫水三叶的刷题日记1 小时前
真的会玩,钉钉前脚辟谣高管凌晨巡查工位,小编随后深夜发文
前端·后端·面试
天天摸鱼的java工程师1 小时前
Go 语言未来会取代 Java 吗?
java·后端