异步任务编排利器CompletableFuture实战

前言

CompletableFuture是Java8中并发包提供的异步编程实现类,它是Future接口的扩展接口,它拥有比Future更强大的能力。

Future接口回顾

Future接口提供下面方法

java 复制代码
public interface Future<V> {
		// 取消任务执行
    boolean cancel(boolean mayInterruptIfRunning);
		// 判断任务是否取消
    boolean isCancelled();
		// 任务是否完成
    boolean isDone();
		// 获取执行结果(阻塞)
    V get() throws InterruptedException, ExecutionException;
		// 最多等待给定的时间获取任务执行结果,否则抛出超时异常
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

Future代码演示

java 复制代码
// 1. 创建线程池
ExecutorService executorService = Executors.newSingleThreadExecutor();
// 2.创建FutureTask任务
FutureTask<String> futureTask = new FutureTask<>(() -> {
    TimeUnit.SECONDS.sleep(5);
    return "任务执行成功";
});
// 3. 将FutureTask提交到线程池
executorService.submit(futureTask);
System.out.println(LocalTime.now() + "--->开始等待执行结果");
// 4. 获取执行结果
String result = futureTask.get();
System.out.println(LocalTime.now() + "<---" + result);
executorService.shutdown();

执行结果如下所示,主线程调用FutureTask.get()方法阻塞,等待了5秒才获取到执行结果

Future接口的局限性

  • Future获取执行结果是阻塞的

Future并不支持回调,要想获取任务执行的结果,只能通过阻塞的get()方法或者轮询判断执行是否成功再获取执行结果

  • 多个Future无法合并

例如我们有一种场景,需要先执行10的Future,等10个Future任务全部执行完毕后,再运行某个函数,Future并不支持

  • Future接口没有异常处理API

Future没有异常处理API,如果抛出异常

CompletableFuture

CompletableFuture是对Future的增强,它不仅支持原来Future的能力,并在此基础上进行了扩展,同时CompletableFuture实现了对任务的编排能力,我们可以使用CompletableFuture提供的API轻松的的编排不同任务的运行顺序。从下面类图可以看到CompletableFuture实现了CompletionStage<T>Future<T>接口。

CompletionStage代表异步计算的一个阶段,它可以是另一个CompletionStage完成时执行一个操作或计算一个值。一个CompletionStage可以是由其他阶段来触发,它完成时也可以触发其他CompletionStage。CompletableFuture实现了CompletionStage接口,使其具备了任务编排的能力

构建CompletableFuture

构建CompletableFuture可以通过下面5个方法

java 复制代码
// 创建一个异步有返回值的CompletableFuture,使用ForkJoinPool.commonPool()执行
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) 
// 创建一个异步有返回值的CompletableFuture,使用自定义线程池执行
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor) 
// 创建一个异步无返回值的CompletableFuture,使用ForkJoinPool.commonPool()执行
public static CompletableFuture<Void> runAsync(Runnable runnable)
// 创建一个异步无返回值的CompletableFuture,使用自定义线程池执行
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor) 
// 创建一个执行结果是value,已经完成的CompletableFuture
public static <U> CompletableFuture<U> completedFuture(U value)

虽然CompletableFuture的构造方法是public的,但是还是不建议直接使用new关键字创建CompletableFuture,通过new关键字构建的CompletableFuture是未完备的CompletableFuture,我们可以通过get()方法的源码就可以看出端倪

java 复制代码
// java.util.concurrent.CompletableFuture#get()
// 调用get()方法时,如果CompletableFuture中result不为空,则会直接返回result
public T get() throws InterruptedException, ExecutionException {
    Object r;
    return reportGet((r = result) == null ? waitingGet(true) : r);
}

// java.util.concurrent.CompletableFuture#completedFuture
// 通过completedFuture传入的参数会作为CompletableFuture的构造参数
public static <U> CompletableFuture<U> completedFuture(U value) {
    return new CompletableFuture<U>((value == null) ? NIL : value);
}
// private构造函数,将入参赋值给result
private CompletableFuture(Object r) {
    this.result = r;
}

从上面源码可以分析出通过new关键字构建的CompletableFuture的未完备指的是没有直接给result赋值或者没有提供Supplier或者Runner,因此还是建议通过上面5个方法构建CompletableFuture

CompletableFuture实现Future能力演示

java 复制代码
// 1. 创建线程池
ExecutorService executors = Executors.newSingleThreadExecutor();
// 2. 创建CompletableFuture
CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
    System.out.println(LocalTime.now() + "--->[" + Thread.currentThread().getName() + "] 开始执行任务");
    try {
        Thread.sleep(5000);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
    System.out.println(LocalTime.now() + "--->[" + Thread.currentThread().getName() + "] 任务执行完毕");
    return "success";
}, executors);
// 3. 通过get方法阻塞获取执行结果
System.out.println(LocalTime.now() + "--->[" + Thread.currentThread().getName() + "] 开始获取执行结果");
String result = completableFuture.get();
System.out.println(LocalTime.now() + "<---[" + Thread.currentThread().getName() + "] 获取执行结果"+result);
// 4. 关闭线程池
executors.shutdown();

执行结果如下图所示,主线程调用get()方法后,等待CompletableFuture中的任务执行完毕后才获取到执行结果,可以看出上面代码执行的过程与Future中提供的能力相似

CompletableFuture任务编排

上面CompletableFuture调用get()方法是阻塞的,需要一直等待任务执行完毕才会返回结果,如果我们想要异步回调,可以使用下面的方法

thenApply()

thenApply总共有下面三个方法,它的作用上一个阶段完成时,接收CompletableFuture执行的结果作为参数,执行Function代码,生成一个新返回值的CompletableFuture

  • thenApply(Function<? super T,? extends U> fn)

CompletableFuture执行完毕后,接收CompletableFuture执行的结果作为参数,执行Function,生成一个新CompletableFuture,这两个任务都是在同一个线程池中运行

  • thenApplyAsync(Function<? super T,? extends U> fn)

CompletableFuture执行完毕后,接收CompletableFuture执行的结果作为参数,异步执行Function,生成一个新返回值的CompletableFuture,默认是ForkJoinPool.commonPool()

  • thenApplyAsync(Function<? super T,? extends U> fn, Executor executor)

CompletableFuture执行完毕后,接收CompletableFuture执行的结果作为参数,使用指定线程池异步执行Function,生成一个新返回值的CompletableFuture

then后面同步和异步是相对于之前的CompletableFuture任务线程池来说的

thenApply()实战演示代码如下,我们先创建一个任务类Task供后续使用,它的主要任务是打印输出任务情况

java 复制代码
public class CommonTask implements Function<String,String>, Consumer<String>,Runnable {
    private final long sleepTime;

    private final String taskName;

    public CommonTask(long sleepTime, String taskName) {
        this.sleepTime = sleepTime;
        this.taskName = taskName;
    }

    @Override
    public String apply(String result) {
        log(result);
        return taskName + " success!";
    }

    @Override
    public void accept(String result) {
        log(result);
    }

    @Override
    public void run() {
        log(null);
    }

    private void log(String result) {
        System.out.println(LocalTime.now() + "--->[" + Thread.currentThread().getName() + "] "+taskName+"执行中,获取到上个任务结果" + result);
        try {
            Thread.sleep(sleepTime);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(LocalTime.now() + "--->[" + Thread.currentThread().getName() + "] "+taskName+"执行完毕!");
   
    }
}

实现ThreadFactory

java 复制代码
public class ThreadFactoryImpl implements ThreadFactory {

    private int i = 0;
    
    private final String namePrefix;

    public ThreadFactoryImpl(String name) {
        this.namePrefix = name;
    }

    @Override
    public Thread newThread(Runnable r) {
        Thread thread = new Thread(r);
        thread.setName(namePrefix+" - "+(++i));
        return thread;
    }
}

我们先创建一个parent CompletableFuture,然后利用thenApply,创建三个子CompletableFuture,任务依赖关系如下

代码如下

java 复制代码
// 1. 创建线程池
ExecutorService mainExecutors = Executors.newSingleThreadExecutor(new ThreadFactoryImpl("parent"));
ExecutorService executors = Executors.newSingleThreadExecutor(new ThreadFactoryImpl("apply executors"));

// 2. 创建CompletableFuture
CompletableFuture<String> cf = CompletableFuture.supplyAsync(new CommonTask(1000,"parent"),mainExecutors);
CompletableFuture<String> cf1 = cf.thenApply(new CommonTask(2500, "thenApply"));
CompletableFuture<String> cf2 = cf.thenApplyAsync(new CommonTask(2000, "thenApplyAsync"));
CompletableFuture<String> cf3 = cf.thenApplyAsync(new CommonTask(1500, "thenApplyAsync executors"), executors);
// 3. 等待cf任务执行
CompletableFuture.allOf(cf1, cf2, cf3).join();

// 4. 关闭线程池
mainExecutors.shutdown();
executors.shutdown();

执行代码,获得如下执行结果,我们可以看出下面三个结论

  • 三个子任务cf1,cf2,cf3开始执行时间与代码中的顺序不一样,
  • thenApply()使用的是parent线程池中的线程执行的
  • thenApplyAsync()如果没有传入指定线程池,默认会使用ForkJoinPool.commonPool
thenAccept()

thenAccept()thenApply()类似,也包含三个方法,不同的是thenAccept()接收的是Consumer任务,下个任务执行完毕后返回CompletableFuture<Void>

  • thenAccept(Consumer<? super T> action)

上个任务完成后,使用上个CcompletableFuture线程池,执行Consumer中的任务

  • thenAcceptAsync(Consumer<? super T> action)

上个任务完成后,异步执行Consumer中的任务,默认是ForkJoinPool.commonPool()

  • thenAcceptAsync(Consumer<? super T> action,Executor executor)

上个任务完成后,使用指定线程池异步执行Consumer中的任务

thenAccept演示代码与thenApply类似,如下所示

java 复制代码
// 1. 创建线程池
ExecutorService mainExecutors = Executors.newSingleThreadExecutor(new ThreadFactoryImpl("parent"));
ExecutorService executors = Executors.newSingleThreadExecutor(new ThreadFactoryImpl("accept executors"));

// 2. 创建CompletableFuture
CompletableFuture<String> cf = CompletableFuture.supplyAsync(new CommonTask(1000,"parent"),mainExecutors);
cf.thenAccept(new CommonTask(2500, "thenAccept"));
cf.thenAcceptAsync(new CommonTask(2000, "thenAcceptAsync"));
cf.thenAcceptAsync(new CommonTask(1500, "thenAcceptAsync executors"), executors);

Thread.sleep(3000);
// 3. 关闭线程池
mainExecutors.shutdown();
executors.shutdown();

执行结果如下

thenRun()

thenRun()也与上面类似,包含三个方法,上个任务执行完毕之后执行Runnable的任务

  • thenRun(Runnable action)

上个任务完成后,使用上个CcompletableFuture线程池,执行Runnable任务

  • thenRunAsync(Runnable action)

上个任务完成后,异步执行Runnable中的任务,默认是ForkJoinPool.commonPool()

  • thenRunAsync(Runnable action,Executor executor)

上个任务完成后,使用指定线程池异步执行Runnable中的任务

thenCombine()

thenCombine()可以合并两个CompletableFuture的执行结果,等待两个任务完成后,将任务结果传给TaskC,再继续执行后续任务

  • thenCombine(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn)

同步合并CompletableFuture执行结果,第一个入参是要合并的CompletionStage,另一个入参是两个CompletableFuture结果合并的BiFunction

  • thenCombineAsync(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn)

异步合并CompletableFuture执行结果,参数与同步的相同

  • thenCombineAsync(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn, Executor executor)

使用指定的线程池异步合并CompletableFuture执行结果,这个方法相比上面两个增加了线程池作为第三个参数

thenAcceptBoth()

theAcceptBoth()可以看做是无返回值版本的thenCombine()

  • thenAcceptBoth(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action)

同步消费CompletableFuture执行结果,第一个入参是要的消费CompletionStage,另一个入参是两个CompletableFuture结果消费的BiConsumer

  • thenAcceptBothAsync(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action)

异步消费CompletableFuture执行结果,参数与同步的相同

  • thenAcceptBothAsync(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action, Executor executor)

使用指定的线程池异步消费CompletableFuture执行结果,这个方法相比上面两个增加了线程池作为第三个参数

为了演示thenCombine的执行效果,我们通过一段比价的代码来理解thenCombine执行结果,任务如下图所示,首先我们通过CompletableFuture创建两个获取价格任务,然后通过thenCombine比较两者的价格,获取最低价

代码如下所示

java 复制代码
public static void main(String[] args) throws ExecutionException, InterruptedException {
    // 1. 创建线程池
    ExecutorService jdExecutors = Executors.newSingleThreadExecutor(new ThreadFactoryImpl("JD executors"));
    ExecutorService taobaoExecutors = Executors.newSingleThreadExecutor(new ThreadFactoryImpl("Taobao executors"));

    // 2. 创建JD CompletableFuture
    CompletableFuture<Integer> jdCf = CompletableFuture.supplyAsync(() -> {
        log("开始获取JD XX商品价格");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        int price = 100;
        log("完成获取JD XX商品价格:"+price);
        return price;
    }, jdExecutors);
    // 3. 创建Taobao CompletableFuture
    CompletableFuture<Integer> taobaoCf = CompletableFuture.supplyAsync(() -> {
        log("开始获取Taobao XX商品价格");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        int price = 150;
        log("完成获取Taobao XX商品价格" + price);
        return price;
    },taobaoExecutors);
    // 4. 比较两者价格
    CompletableFuture<Integer> finnalCf = jdCf.thenCombine(taobaoCf, (jdPrice, taobaoPrice) -> {
        log("开始比价,JD price:" + jdPrice + "  taobao Price:" + taobaoPrice);
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        int minPrice = Math.min(jdPrice, taobaoPrice);
        log("完成比价,最低价格:" + minPrice);
        return minPrice;
    });

    // 5. 关闭线程池
    jdExecutors.shutdown();
    taobaoExecutors.shutdown();
}

private static void log(String msg) {
    System.out.println(LocalTime.now() + "--->["+Thread.currentThread().getName()+"] "+msg);
}

执行结果如下所示,在调用thenCombine方法时并没有传入指定线程池,默认采用JD executors

runAfterBoth()

runAfterBoth()可以看做是无入参,无返回值版本的thenCombine()

  • runAfterBoth(CompletionStage<?> other,Runnable action)

等待两个CompletableFuture执行完毕,同步执行另一个Runnable任务

  • runAfterBoth(CompletionStage<?> other,Runnable action)

等待两个CompletableFuture执行完毕,异步执行另一个Runnable任务

  • runAfterBothAsync(CompletionStage<?> other,Runnable action,Executor executor)

等待两个CompletableFuture执行完毕,使用指定线程池异步执行另一个Runnable任务

runAfterBoth()thenAcceptBoth()类似,可以参考上面例子,这里就不再单独演示了

applyToEither()

两个任务执行,哪个任务执行的快,就会使用那个任务的结果,子任务处理之后生成新的结果

  • applyToEither(CompletionStage<? extends T> other, Function<? super T, U> fn)

同步执行子任务的方法,子任务使用的线程池与调用applyToEither方法的CF任务使用相同的线程池

这里会有一个问题,同步执行子任务的方法会用哪个线程来执行呢?前面演示的thenCombine()采用的是调用thenCombine()方法CF的线程池

  • applyToEitherAsync(CompletionStage<? extends T> other, Function<? super T, U> fn)

异步执行子任务的方法,采用ForkJoinPool.commonPool()线程池

  • applyToEitherAsync(CompletionStage<? extends T> other, Function<? super T, U> fn,Executor executor)

使用指定线程池异步执行子任务

acceptEither()

两个任务执行,哪个任务执行的快,就会消费那个结果,不会有返回值

  • acceptEither(CompletionStage<? extends T> other, Consumer<? super T> action)

同步消费子任务的方法

  • acceptEitherAsync(CompletionStage<? extends T> other, Consumer<? super T> action)

异步消费子任务的方法,采用ForkJoinPool.commonPool()线程池

  • acceptEitherAsync(CompletionStage<? extends T> other, Consumer<? super T> action,Executor executor)

使用指定线程池异步消费子任务的方法

我们依然采用上面比价的例子演示,分别创建jdCf和taobaoCf两个获取价格的CompletableFuture,然后我们使用acceptEither方法打印出先获取到的价格

java 复制代码
public static void main(String[] args) throws ExecutionException, InterruptedException {
    // 1. 创建线程池
    ExecutorService jdExecutors = Executors.newSingleThreadExecutor(new ThreadFactoryImpl("JD executors"));
    ExecutorService taobaoExecutors = Executors.newSingleThreadExecutor(new ThreadFactoryImpl("Taobao executors"));

    // 2. 创建JD CompletableFuture
    CompletableFuture<Integer> jdCf = CompletableFuture.supplyAsync(() -> {
        log("开始获取JD XX商品价格");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        int price = 100;
        log("完成获取JD XX商品价格:"+price);
        return price;
    }, jdExecutors);
    // 3. 创建Taobao CompletableFuture
    CompletableFuture<Integer> taobaoCf = CompletableFuture.supplyAsync(() -> {
        log("开始获取Taobao XX商品价格");
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        int price = 150;
        log("完成获取Taobao XX商品价格" + price);
        return price;
    },taobaoExecutors);
    // 4. 打印出最先获取的价格
    jdCf.acceptEither(taobaoCf, (fastResult) -> {
        log("完成价格获取,更快获取的价格为:" + fastResult);
    });

    // 5. 关闭线程池
    jdExecutors.shutdown();
    taobaoExecutors.shutdown();
}

private static void log(String msg) {
    System.out.println(LocalTime.now() + "--->["+Thread.currentThread().getName()+"] "+msg);
}

演示结果如下所示

从上面结果可以看到,执行子任务的线程池是先执行完任务的JD executors,那么我们调整上面任务的时间,将获取taobao价格的时间设置为1000ms,这样会先执行完taobaoCf,执行结果如下所示,执行子任务的线程池又换成了Taobao executors

从上面的结果我们可以得到结论,同步执行的acceptEither()会采用先执行完任务CompletableFuture的线程池执行子任务

runAfterEither()

两个任务执行,只要任意一个任务执行完毕,就会开始执行下一个任务(Runnable)

  • runAfterEither(CompletionStage<?> other,Runnable action)

只要一个任务执行完毕,则同步执行子任务

  • runAfterEitherAsync(CompletionStage<?> other,Runnable action)

采用ForkJoinPool.commonPool()线程池执行子任务

  • runAfterEitherAsync(CompletionStage<?> other,Runnable action,Executor executor)

使用指定线程池执行子任务

whenComplete()

whenComplete()是当任务执行完毕之后的回调方法,会将执行结果或者任务运行期间的异常传递给回调方法,如果正常执行,则异常为空,whenComplete()与上面类似,有1个同步方法,2个异步方法

  • whenComplete(BiConsumer<? super T, ? super Throwable> action)

CompletableFuture任务完成后,同步执行whenComplete()回调

  • whenCompleteAsync(BiConsumer<? super T, ? super Throwable> action)

采用ForkJoinPool.commonPool()线程池,异步执行回调

  • whenCompleteAsync(BiConsumer<? super T, ? super Throwable> action, Executor executor)

指定线程池异步执行回调

handle()

handle()whenComplete()方法类似,也是当任务任务完成时执行回调方法,区别是whenComplete()没有返回值,handle()有返回值

  • handle(BiFunction<? super T, Throwable, ? extends U> fn)

同步handle()

  • handleAsync(BiFunction<? super T, Throwable, ? extends U> fn)

采用ForkJoinPool.commonPool()线程池,回调handle()

  • handleAsync(BiFunction<? super T, Throwable, ? extends U> fn,Executor executor)

指定线程池异步回调handle()

exceptionally()

CompletableFuture任务完成时如果发生异常,则后续任务不会执行,CompletableFuture会将异常传给exceptionally()处理

  • exceptionally(Function<Throwable, ? extends T> fn)
allOf()

返回一个新的CompletableFuture,当所有给定的ComplettableFuture完成时,该新的ComplextableFuture已完成。如果任何给定的CompletableFutures异常完成,那么返回的ComplettableFuture也会这样做,CompletionException将此异常作为其原因。

java 复制代码
CompletableFuture<Void> allOf(CompletableFuture<?>... cfs)

allOf()使用场景是多个任务全部执行完毕后,在执行后续任务,例如我们有三个任务taskA,taskB,taskC,要在这三个任务执行完之后再执行finalTask任务

代码如下所示

java 复制代码
// 1.创建三个taskA,taskB,taskC completableFuture
CompletableFuture<String> taskACf = CompletableFuture.supplyAsync(new CommonTask(1000, "taskA"));
CompletableFuture<String> taskBCf = CompletableFuture.supplyAsync(new CommonTask(2000, "taskB"));
CompletableFuture<String> taskCCf = CompletableFuture.supplyAsync(new CommonTask(3000, "taskC"));
// 2. 使用allOf
CompletableFuture<Void> allOfCf = CompletableFuture.allOf(taskACf, taskBCf, taskCCf);
// 3. taskA,taskB,taskC执行完毕之后执行finalTask
CompletableFuture<Void> finalCf = allOfCf.thenRun(new CommonTask(2000, "finalTask"));
finalCf.get();

执行结果如下所示,可以看到taskA,tackB,taskC执行耗时不同,使用allOf()合并后,finalTask会等待taskA,taskB,taskC执行完毕后再执行

假设taskA,tackB,taskC中只要有一个任务执行失败,这三个任务相互不会影响,而finalTask则不会执行,还是上面例子,假设taskB执行过程中抛出异常,我们可以观察finalTask执行情况,代码如下所示

java 复制代码
// 1.创建三个不同的CF任务
CompletableFuture<String> taskACf = CompletableFuture.supplyAsync(new CommonTask(1000, "taskA"));
CompletableFuture<String> taskBCf = CompletableFuture.supplyAsync(()->{
    System.out.println(LocalTime.now() + "--->[" + Thread.currentThread().getName() + "] taskB执行中");
    try {
        Thread.sleep(1500);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
    throw new RuntimeException("任务B执行失败");
});
CompletableFuture<String> taskCCf = CompletableFuture.supplyAsync(new CommonTask(3000, "taskC"));

// 2. 使用allOf
CompletableFuture<Void> allOfCf = CompletableFuture.allOf(taskACf, taskBCf, taskCCf);
// 3. taskA,taskB,taskC执行完毕之后执行finalTask
CompletableFuture<Void> finalCf = allOfCf.thenRun(new CommonTask(2000, "finalTask"))
        .exceptionally((e)->{
            System.out.println("final捕获到异常:"+e);
            return null;
        });
finalCf.get();

执行结果如下,我们可以看到finalTask并没有执行,exceptionally()捕获到taskB抛出的异常

其实还有更极端的场景是假设多个任务都抛出异常了,调用allOf()CompletableFuture会捕获哪个任务抛出的异常呢?大家可以实践一下

anyOf()

了解了allOf()方法,我们再来学习anyOf()就会简单很多,anyOf()就是上游任务只要有一个任务执行完了,就会执行后续任务,还是拿上面的例子taskA,tackB,taskC只要有一个任务执行完毕之后就执行finalTask

anyOf()allOf()不同的是anyOf()方法获取的CompletableFuture<Object>会返回最快执行完成任务的结果

java 复制代码
// 1.创建三个不同的CF任务
CompletableFuture<String> taskACf = CompletableFuture.supplyAsync(new CommonTask(1000, "taskA"));
CompletableFuture<String> taskBCf = CompletableFuture.supplyAsync(new CommonTask(2000, "taskB"));
CompletableFuture<String> taskCCf = CompletableFuture.supplyAsync(new CommonTask(3000, "taskC"));

// 2. 使用anyOf
CompletableFuture<Object> anyOfCf = CompletableFuture.anyOf(taskACf, taskBCf, taskCCf);
// 3. taskA,taskB,taskC只要一个任务执行完毕之后,就执行anyOf
CompletableFuture<Void> finalCf = anyOfCf.thenAccept((r)->{
    System.out.println(LocalTime.now() + "--->[" + Thread.currentThread().getName() + "] "+"finalTask执行中,获取到上个任务结果" + r);
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
    System.out.println(LocalTime.now() + "--->[" + Thread.currentThread().getName() + "] "+"finalTask执行完毕!");
});
finalCf.get();

执行结果如下,taskA最快执行完毕,taskA执行完毕之后,finalTask立即开始执行了

anyOf()allOf()处理异常方式对比

anyOf()处理异常的方式与allOf()并不相同,在还没有任务完成前其中一个发生异常,anyOf()中的任务并不会相互影响,后续任务不会执行,异常将会抛给exceptionally()处理。如果已经有任务完成了,后续任务如果发生异常,不会对后续任务有影响,后续任务还可以正常执行。

相关推荐
zquwei10 分钟前
SpringCloudGateway+Nacos注册与转发Netty+WebSocket
java·网络·分布式·后端·websocket·网络协议·spring
TT哇16 分钟前
*【每日一题 提高题】[蓝桥杯 2022 国 A] 选素数
java·算法·蓝桥杯
火烧屁屁啦39 分钟前
【JavaEE进阶】初始Spring Web MVC
java·spring·java-ee
w_31234541 小时前
自定义一个maven骨架 | 最佳实践
java·maven·intellij-idea
岁岁岁平安1 小时前
spring学习(spring-DI(字符串或对象引用注入、集合注入)(XML配置))
java·学习·spring·依赖注入·集合注入·基本数据类型注入·引用数据类型注入
武昌库里写JAVA1 小时前
Java成长之路(一)--SpringBoot基础学习--SpringBoot代码测试
java·开发语言·spring boot·学习·课程设计
Q_19284999061 小时前
基于Spring Boot的九州美食城商户一体化系统
java·spring boot·后端
张国荣家的弟弟1 小时前
【Yonghong 企业日常问题 06】上传的文件不在白名单,修改allow.jar.digest属性添加允许上传的文件SH256值?
java·jar·bi
ZSYP-S2 小时前
Day 15:Spring 框架基础
java·开发语言·数据结构·后端·spring
yuanbenshidiaos2 小时前
C++----------函数的调用机制
java·c++·算法