Java 一文讲清楚 CompletableFuture

news2024/9/20 17:54:31

Callable 和 Future

创建一个Java线程的三种方式,其中继承Thread类或实现Runnable接口都可以创建线程,但这两种方法都有一个问题就是:没有返回值,不能获取执行完的结果。因此后面在JDK1.5才新增了一个Callable接口来解决上面的问题,而Future和FutureTask就可以与Callable配合起来使用。

Callable能在线程池中提交任务使用,只能在submit()和invokeAnay()以及invokeAll()这三个任务提交的方法中使用,如果需要直接使用Thread的方式启动线程,则需要使用FutureTask对象作为Thread的构造参数,而FutureTask的构造参数又是Callable的对象

public static class CallerTask implements Callable<String>{

    @Override
    public String call() throws Exception {
        return "callable创建线程";
    }
}

public static void main(String[] args) throws Exception {
    FutureTask<String> futureTask  = new FutureTask<String>(new CallerTask());
    /*String s1 = futureTask.get();*/
    /*System.out.println("first"+ s1);*/
    new Thread(futureTask).start();
    String s = futureTask.get();
    System.out.println(s);

}
  • Future接口定义了操作异步任务执行一些方法,如获取异步任务的执行结果、取消任务的执行、判断任务是否被取消、判断任务执行是否完毕等。
  • Callable接口中定义了需要有返回的任务需要实现的方法。

从Future到FutureTask

Future的源码及其定义:Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果

/**
 * A {@code Future} represents the result of an asynchronous
 * computation.  Methods are provided to check if the computation is
 * complete, to wait for its completion, and to retrieve the result of
 * the computation.  The result can only be retrieved using method
 * {@code get} when the computation has completed, blocking if
 * necessary until it is ready.  Cancellation is performed by the
 * {@code cancel} method.  Additional methods are provided to
 * determine if the task completed normally or was cancelled. Once a
 * computation has completed, the computation cannot be cancelled.
 * If you would like to use a {@code Future} for the sake
 * of cancellability but not provide a usable result, you can
 * declare types of the form {@code Future<?>} and
 * return {@code null} as a result of the underlying task.
 */
public interface Future<V> {
	// 取消任务的执行,参数表示是否立即中断任务执行,或者等任务结束
    boolean cancel(boolean mayInterruptIfRunning);
	// 任务是否已经取消,任务完成前将其取消,则返回true
    boolean isCancelled();
	// 任务是否已经完成
    boolean isDone();
    // 等待任务执行结束,返回泛型结果.中断或任务执行异常都会抛出异常
    V get() throws InterruptedException, ExecutionException;
	// 同上面的get功能一样,多了设置超时时间。参数timeout指定超时时间,uint指定时间的单位,在枚举类TimeUnit中有相关的定义。如果计算超时,将抛出TimeoutException
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}
  • get( ) 阻塞:一旦调用get( )方法,不管是否计算完成都会导致阻塞。
  • isDone( ) 轮询:轮询的方式会耗费无谓的CPU资源,而且也不见得能及时地得到计算结果。如果想要异步获取结果,通常都会以轮询的方式去获取结果尽量不要阻塞

Future归根结底只是一个接口,而FutureTask实现了这个接口,同时还实现了Runnalbe接口,这样FutureTask就相当于是消费者和生产者的桥梁了,消费者可以通过FutureTask存储任务的执行结果。

public class FutureTask<V> implements RunnableFuture<V> {
    private volatile int state;
    private static final int NEW          = 0;
    private static final int COMPLETING   = 1;
    private static final int NORMAL       = 2;
    private static final int EXCEPTIONAL  = 3;
    private static final int CANCELLED    = 4;
    private static final int INTERRUPTING = 5;
    private static final int INTERRUPTED  = 6;

    /** The underlying callable; nulled out after running */
    // 任务
    private Callable<V> callable;
    /** The result to return or exception to throw from get() */
    // 执行结果或异常
    private Object outcome; // non-volatile, protected by state reads/writes
    /** The thread running the callable; CASed during run() */
    // 执行任务的线程
    private volatile Thread runner;
    /** Treiber stack of waiting threads */
    private volatile WaitNode waiters;
}

public interface RunnableFuture<V> extends Runnable, Future<V> {
    /**
     * Sets this Future to the result of its computation
     * unless it has been cancelled.
     */
    void run();
}

FutureTask使用

这里展示一个实际的应用场景,平常在使用购物软件抢购促销产品的时候,需要查看商品信息(包括商品基本信息、商品价格、商品库存、商品图片)。而这些信息一般都分布在不同的业务中心,由不同的系统提供服务。如果采用同步方式,假设一个接口需要50ms,那么一个商品查询下来就需要200ms-300ms,这对于我们来说是不满意的。如果使用Future改造则需要的就是最长耗时服务的接口,也就是50ms左右。

如果采用同步方式,假设一个接口需要50ms,那么一个商品查询下来就需要200ms-300ms,这对于我们来说是不满意的。如果使用Future改造则需要的就是最长耗时服务的接口,也就是50ms左右。

Future的注意事项:

  • 当 for 循环批量获取Future的结果时容易 block,get 方法调用时应使用 timeout 限制
  • Future 的生命周期不能后退。一旦完成了任务,它就永久停在了“已完成”的状态,不能从头再来

Future的局限性:

  • 并发执行多任务:Future只提供了get()方法来获取结果,并且是阻塞的。
  • 无法对多个任务进行链式调用:如果你希望在计算任务完成后执行特定动作,比如发邮件,但Future却没有提供这样的能力;
  • 无法组合多个任务:如果你运行了10个任务,并期望在它们全部执行结束后执行特定动作,那么在Future中这是无能为力的;
  • 没有异常处理:Future接口中没有关于异常处理的方法;

而这些局限性CompletionService和CompletableFuture都解决了。

CompletionService使用

Callable+Future虽然可以实现多个task并行执行,但是如果遇到前面的task执行较慢时需要阻塞等待前面的task执行完后面task才能取得结果。而CompletionService的主要功能就是一边生成任务,一边获取任务的返回值。让两件事分开执行,任务之间不会互相阻塞,可以实现先执行完的先取结果,不再依赖任务顺序了。

CompletionService内部通过阻塞队列+FutureTask,实现了任务先完成可优先获取到,即结果按照完成先后顺序排序,内部有一个先进先出的阻塞队列,用于保存已经执行完成的Future,通过调用它的take()或poll()可以获取到一个已经执行完成的Future,进而通过调用Future接口实现类的get方法获取最终的结果。

使用案例分析

电商询价

直接Future方式

向不同电商平台询价,并保存价格,采用ThreadPoolExecutor+Future的方案:异步执行询价然后再保存

//    创建线程池 
ExecutorService    executor = Executors.newFixedThreadPool(2); 
//    异步向电商S1询价 
Future<Integer>    f1 = executor.submit(()->getPriceByS1()); 
//    异步向电商S2询价 
Future<Integer>    f2=    executor.submit(()->getPriceByS2());             
//    获取电商S1报价并异步保存 
executor.execute(()->save(f1.get()));        
//    获取电商S2报价并异步保存 
executor.execute(()->save(f2.get())   

如果获取电商S1报价的耗时很长,那么即便获取电商S2报价的耗时很短,也无法让保存S2报价的操作先执行,因为这个主线程都阻塞 在了f1.get()操作上。

CompletionService方式

使用CompletionService实现先获取的报价先保存到数据库

//创建线程池
ExecutorService executor = Executors.newFixedThreadPool(10);
//创建CompletionService
CompletionService<Integer> cs = new ExecutorCompletionService<>(executor);
//异步向电商S1询价
cs.submit(() -> getPriceByS1());
//异步向电商S2询价
cs.submit(() -> getPriceByS2());
//将询价结果异步保存到数据库
for (int i = 0; i < 2; i++) {
    Integer r = cs.take().get();
    executor.execute(() -> save(r));
}

Dobbo的Forking Cluter场景

Dubbo中有一种叫做Forking的集群模式,这种集群模式下,支持并行地调用多个服务实例,只要有一个成功就返回结果。

通过CompletionService来实现这种Forking集群模式

// 创建线程池
ExecutorService executor = Executors.newFixedThreadPool(3);
// 创建CompletionService
CompletionService<Integer> cs = new ExecutorCompletionService<>(executor);
// 用于保存Future对象
List<Future<Integer>> futures = new ArrayList<>(3);
//提交异步任务,并保存future到futures 
futures.add(cs.submit(()->geocoderByS1()));
futures.add(cs.submit(()->geocoderByS2()));
futures.add(cs.submit(()->geocoderByS3()));
// 获取最快返回的任务执行结果
Integer r = 0;
try {
  // 只要有一个成功返回,则break
  for (int i = 0; i < 3; ++i) {
    r = cs.take().get();
    //简单地通过判空来检查是否成功返回
    if (r != null) {
      break;
    }
  }
} finally {
  //取消所有任务
  for(Future<Integer> f : futures)
    f.cancel(true);
}
// 返回结果
return r;

应用场景总结

  • 当需要批量提交异步任务的时候建议使用CompletionService。CompletionService将线程池Executor和阻塞队列BlockingQueue的功能融合在了一起,能够让批量异步任务的管理更简单。
  • CompletionService能够让异步任务的执行结果有序化。先执行完的先进入阻塞队列,利用这个特性,你可以轻松实现后续处理的有序性,避免无谓的等待,同时还可以快速实现诸如Forking Cluster这样的需求。
  • 线程池隔离。CompletionService支持自己创建线程池,这种隔离性能避免几个特别耗时的任务拖垮整个应用的风险。

CompletableFuture

前面介绍过了Future的局限性,它没法直接对多个任务进行链式、组合等处理,需要借助并发工具类才能完成,实现逻辑比较复杂。

而CompletableFuture是对Future的扩展和增强。CompletableFuture实现了Future接口,并在此基础上进行了丰富的扩展,完美弥补了Future的局限性,同时CompletableFuture实现了对任务编排的能力。借助这项能力,可以轻松地组织不同任务的运行顺序、规则以及方式。

CompletableFuture的继承结构如下:

CompletionStage接口定义了任务编排的方法,代表异步计算过程中的某一个阶段,一个阶段完成以后可能会触发另外一个阶段,有些类似Linux系统的管道分隔符传参数。

异步执行的,默认线程池是ForkJoinPool.commonPool(),但为了业务之间互不影响,且便于定位问题,强烈推荐使用自定义线程池。

主线程不要立刻结束,否则CompletableFuture默认使用的线程池会立刻关闭:暂停3秒钟线程

CompletableFuture的优点

  • 异步任务结束时,会自动回调某个对象的方法;
  • 异步任务出错时,会自动回调某个对象的方法;
  • 主线程设置好回调后,不再关心异步任务的执行,异步任务之间可以顺序执行

函数式编程说明

功能

常用方法

  • 依赖关系
    • thenApply():把前面任务的执行结果,交给后面的Function
    • thenCompose():用来连接两个有依赖关系的任务,结果由第二个任务返回
  • and集合关系
    • thenCombine():合并任务,有返回值
    • thenAccepetBoth():两个任务执行完成后,将结果交给thenAccepetBoth处理,无返回值
    • runAfterBoth():两个任务都执行完成后,执行下一步操作(Runnable类型任务)
  • or聚合关系
    • applyToEither():两个任务哪个执行的快,就使用哪一个结果,有返回值
    • acceptEither():两个任务哪个执行的快,就消费哪一个结果,无返回值
    • runAfterEither():任意一个任务执行完成,进行下一步操作(Runnable类型任务)
  • 并行执行
    • allOf():当所有给定的 CompletableFuture 完成时,返回一个新的 CompletableFuture
    • anyOf():当任何一个给定的CompletablFuture完成时,返回一个新的CompletableFuture
  • 结果处理
    • whenComplete:当任务完成时,将使用结果(或 null)和此阶段的异常(或 null如果没有)执行给定操作(异步)
    • exceptionally:返回一个新的CompletableFuture,当前面的CompletableFuture完成时,它也完成,当它异常完成时,给定函数的异常触发这个CompletableFuture的完成

异步操作

CompletableFuture提供了四个静态方法来创建一个异步操作:

public static CompletableFuture<Void> runAsync(Runnable runnable)
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executr)

这四个方法的区别:

  • runAsync() 以Runnable函数式接口类型为参数,没有返回结果,
  • supplyAsync() 以Supplier函数式接口类型为参数,返回结果类型为U;Supplier接口的 get()是有返回值的(会阻塞)
  • 使用没有指定Executor的方法时,内部使用ForkJoinPool.commonPool() 作为它的线程池执行异步代码。如果指定线程池,则使用指定的线程池运行。
  • 默认情况下CompletableFuture会使用公共的ForkJoinPool线程池,这个线程池默认创建的线程数是 CPU 的核数(也可以通过 JVM option:-Djava.util.concurrent.ForkJoinPool.common.parallelism 来设置ForkJoinPool线程池的线程数)。如果所有CompletableFuture共享一个线程池,那么一旦有任务执行一些很慢的 I/O 操作,就会导致线程池中所有线程都阻塞在 I/O 操作上,从而造成线程饥饿,进而影响整个系统的性能。所以,强烈建议你要根据不同的业务类型创建不同的线程池,以避免互相干扰。
Runnable runnable = () -> System.out.println("无返回结果异步任务");
CompletableFuture.runAsync(runnable);

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    System.out.println("有返回值的异步任务");
    try {
        Thread.sleep(5000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return "Hello World";
});
String result = future.get();

获取结果(join&get)

  • join()和get()方法都是用来获取CompletableFuture异步之后的返回值。
  • join()方法抛出的是uncheck异常(即未经检查的异常),不会强制开发者抛出。
  • get()方法抛出的是经过检查的异常,ExecutionException, InterruptedException 需要用户手动处理(抛出或者 try catch)

结果处理

当CompletableFuture的计算结果完成,或者抛出异常的时候,我们可以执行特定的 Action。主要是下面的方法:

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)

Action的类型是BiConsumer<? super T,? super Throwable>,它可以处理正常的计算结果,或者异常情况。方法不以Async结尾,意味着Action使用相同的线程执行,而Async可能会使用其它的线程去执行(如果使用相同的线程池,也可能会被同一个线程选中执行)。

这几个方法都会返回CompletableFuture,当Action执行完毕后它的结果返回原始的CompletableFuture的计算结果或者返回异常

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    try {
        TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
    }
    if (new Random().nextInt(10) % 2 == 0) {
        int i = 12 / 0;
    }
    System.out.println("执行结束!");
    return "test";
});
// 任务完成或异常方法完成时执行该方法
// 如果出现了异常,任务结果为null
future.whenComplete(new BiConsumer<String, Throwable>() {
    @Override
    public void accept(String t, Throwable action) {
        System.out.println(t+" 执行完成!");
    }
});
// 出现异常时先执行该方法
future.exceptionally(new Function<Throwable, String>() {
    @Override
    public String apply(Throwable t) {
        System.out.println("执行失败:" + t.getMessage());
        return "异常xxxx";
    }
});

future.get();

上面的代码当出现异常时,输出结果如下

执行失败:java.lang.ArithmeticException: / by zero
null 执行完成!

应用场景

结果转换

将上一段任务的执行结果作为下一阶段任务的入参参与重新计算,产生新的结果。

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)
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
    int result = 100;
    System.out.println("第一次运算:" + result);
    return result;
}).thenApply(number -> {
    int result = number * 3;
    System.out.println("第二次运算:" + result);
    return result;
});

thenCompose:的参数为一个返回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) ;

具体使用:

CompletableFuture<Integer> future = CompletableFuture
.supplyAsync(new Supplier<Integer>() {
    @Override
    public Integer get() {
        int number = new Random().nextInt(30);
        System.out.println("第一次运算:" + number);
        return number;
    }
})
.thenCompose(new Function<Integer, CompletionStage<Integer>>() {
    @Override
    public CompletionStage<Integer> apply(Integer param) {
        return CompletableFuture.supplyAsync(new Supplier<Integer>() {
            @Override
            public Integer get() {
                int number = param * 2;
                System.out.println("第二次运算:" + number);
                return number;
            }
        });
    }
});

thenApply 和 thenCompose的区别:

  • thenApply转换的是泛型中的类型,返回的是同一个CompletableFuture;
  • thenCompose将内部的CompletableFuture调用展开来并使用上一个CompletableFutre调用的结果在下一步的CompletableFuture调用中进行运算,是生成一个新的CompletableFuture。

结果消费

与结果处理和结果转换系列函数返回一个新的CompletableFuture不同,结果消费系列函数只对结果执行Action,而不返回新的计算值。

根据对结果的处理方式,结果消费函数又可以分为下面三大类:

  • thenAccept():对单个结果进行消费
  • thenAcceptBoth():对两个结果进行消费
  • thenRun():不关心结果,只对结果执行Action

thenAccept

观察该系列函数的参数类型可知,它们是函数式接口Consumer,这个接口只有输入,没有返回值。

常用方法:

public CompletionStage<Void> thenAccept(Consumer<? super T> action);
public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action);

具体使用

CompletableFuture<Void> future = CompletableFuture
    .supplyAsync(() -> {
        int number = new Random().nextInt(10);
        System.out.println("第一次运算:" + number);
        return number;
    }).thenAccept(number ->
                  System.out.println("第二次运算:" + number * 5));

thenAcceptBoth 函数的作用是,当两个CompletionStage都正常完成计算的时候,就会执行提供的action消费两个异步的结果。

常用方法:

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);

具体使用:

CompletableFuture<Integer> futrue1 = CompletableFuture.supplyAsync(new Supplier<Integer>() {
            @Override
            public Integer get() {
                int number = new Random().nextInt(3) + 1;
                System.out.println("任务1结果:" + 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("任务2结果:" + number);
                return number;
            }
        });

        futrue1.thenAcceptBoth(future2, new BiConsumer<Integer, Integer>() {
            @Override
            public void accept(Integer x, Integer y) {
                System.out.println("最终结果:" + (x + y));
            }
        }).get();

thenRun:也是对线程任务结果的一种消费函数,与thenAccept不同的是,thenRun会在上一阶段 CompletableFuture计算完成的时候执行一个Runnable,而Runnable并不使用该CompletableFuture计算的结果。

常用方法

public CompletionStage<Void> thenRun(Runnable action);
public CompletionStage<Void> thenRunAsync(Runnable action);

具体使用

CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> {
    int number = new Random().nextInt(10);
    System.out.println("第一阶段:" + number);
    return number;
}).thenRun(() ->
           System.out.println("thenRun 执行"));

结果组合

thenCombine 合并两个线程任务的结果,并进一步处理。

常用方法:

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

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

public <U,V> CompletableFuture<V> thenCombineAsync(CompletionStage<? extends U> other,BiFunction<? super T,

具体使用

CompletableFuture<Integer> future1 = CompletableFuture
                .supplyAsync(() -> {
                    int number = new Random().nextInt(10);
                    System.out.println("任务1结果:" + number);
                    return number;
                });
CompletableFuture<Integer> future2 = CompletableFuture
        .supplyAsync(() -> {
            int number = new Random().nextInt(10);
            System.out.println("任务2结果:" + number);
            return number;
        });
CompletableFuture<Integer> result = future1
        .thenCombine(future2, Integer::sum);
System.out.println("组合后结果:" + result.get());

任务交互

线程交互指将两个线程任务获取结果的速度相比较,按一定的规则进行下一步处理。

applyToEither

两个线程任务相比较,先获得执行结果的,就对该结果进行下一步的转化操作。

常用方法:

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

具体使用:

 CompletableFuture<Integer> future1 = CompletableFuture
                .supplyAsync(new Supplier<Integer>() {
                    @Override
                    public Integer get() {
                        int number = new Random().nextInt(10);
                        System.out.println("任务1结果:" + number);
                        return number;
                    }
                });
        CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(new Supplier<Integer>() {
            @Override
            public Integer get() {
                int number = new Random().nextInt(10);
                System.out.println("任务2结果:" + number);
                return number;
            }
        });

        CompletableFuture<Integer> future = future1.applyToEither(future2, new Function<Integer, Integer>() {
            @Override
            public Integer apply(Integer number) {
                System.out.println("最快结果:" + number);
                return number * 2;
            }
        });

        System.out.println(future.get());

acceptEither

两个线程任务相比较,先获得执行结果的,就对该结果进行下一步的消费操作。

常用方法:

public CompletionStage<Void> acceptEither(CompletionStage<? extends T> other,Consumer<? super T> action);
public CompletionStage<Void> acceptEitherAsync(CompletionStage<? extends T> other,Consumer<? super T> action);

具体使用

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;
            }
        });

        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;
            }
        });

        future1.acceptEither(future2, new Consumer<Integer>() {
            @Override
            public void accept(Integer number) {
                System.out.println("最快结果:" + number);
            }
        }).get();

runAfterEither

两个线程任务相比较,有任何一个执行完成,就进行下一步操作,不关心运行结果。

常用方法:

public CompletionStage<Void> runAfterEither(CompletionStage<?> other,Runnable action);
public CompletionStage<Void> runAfterEitherAsync(CompletionStage<?> other,Runnable action);

anyOf

anyOf() 的参数是多个给定的 CompletableFuture,当其中的任何一个完成时,方法返回这个 CompletableFuture。

常用方法:

public static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs)

具体使用:

Random random = new Random();
        CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(random.nextInt(5));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "hello";
        });

        CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(random.nextInt(5));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "world";
        });
        CompletableFuture<String> future3 = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(random.nextInt(5));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "for java";
        });
        CompletableFuture<Object> result = CompletableFuture.anyOf(future1, future2, future3);
        System.out.println(result.get());

allOf

allOf 方法用来实现多 CompletableFuture 的同时返回。

常用方法:

public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs)

具体使用:

CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("future1完成!");
            return "future1完成!";
        });

        CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
            System.out.println("future2完成!");
            return "future2完成!";
        });

        CompletableFuture<Void> future = CompletableFuture.allOf(future1, future2);


        future.get();

比价案例实战Case

package com.redis7.elasticsearch;

import lombok.Getter;

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 1. 需求说明
 *    1. 同一款产品,同时搜索出同款产品在各大电商平台的售价
 *    2. 同一款产品,同时搜索出本产品在同一个电商平台下,各个入驻卖家售价是多少
 * 2. 输出返回
 *    出来希望时同款产品在不同地方的加个清单列表,返回一个List<String>
 *    《mysql》 in jd price is 88.05
 *    《mysql》 in dangdang price us 86.11
 *    《mysql》 in taobao price is 90.43
 * 3. 解决方案,比对同一个商品在各个平台上的价格,要求获得一个清单列表
 *    1. step by step,按部就班,查完京东查淘宝,查完淘宝查天猫....
 *    2. all in 万箭齐发,一口气多线程异步任务同时查询....
 * @author haoll
 * @create 2023-06-02 17:10
 */
public class CompletableFutureMallDemo {

    static List<NetMall> list = Arrays.asList(
            new NetMall("jd"),
            new NetMall("dangdang"),
            new NetMall("taobao")
    );

    /**
     * step by step 一家一家查
     * List<NetMall> ----> map ----> List<String>
     *@Param [list, productName]
     *@Return
     */
    public static List<String> getPrice(List<NetMall> list, String productName){
        //《mysql》 in taobao price is 90.43
        return list
                .stream()
                .map(netMall ->
                        String.format(productName + "in %s price is %.2f",//%s是占位符
                                netMall.getNetMallName(),
                                netMall.calcPrice(productName)))
                .collect(Collectors.toList());
    }

    /**
     * List<NetMall> ----> List<CompletableFuture<String>> ----> List<String>
     *@Param [list, productName]
     *@Return
     */
    public static List<String> getPriceByCompletableFuture(List<NetMall> list, String productName){
        return list.stream().map(netMall -> CompletableFuture.supplyAsync(() -> String.format(productName + "in %s price is %.2f",//%s是占位符
                        netMall.getNetMallName(),
                        netMall.calcPrice(productName))))
                .collect(Collectors.toList())
                .stream().map(CompletableFuture::join)
                .collect(Collectors.toList());
    }

    public static void main(String[] args) {

        long startTime = System.currentTimeMillis();
        List<String> list1 = getPrice(list, "mysql");
        for (String element : list1){
            System.out.println(element);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("----costTime: " + (endTime - startTime) + "毫秒");
        /* mysqlin jd price is 110.26 mysqlin dangdang price is 110.27 mysqlin taobao price is 109.32
            ----costTime: 3148毫秒
         */

        System.out.println("----------------------");

        long startTime2 = System.currentTimeMillis();
        List<String> list2 = getPriceByCompletableFuture(list, "mysql");
        for (String element : list2){
            System.out.println(element);
        }//mysqlin jd price is 110.3 mysqlin dangdang price is 110.4 mysqlin taobao price is 110.77
        //----costTime: 1030毫秒
        long endTime2 = System.currentTimeMillis();
        System.out.println("----costTime: " + (endTime2 - startTime2) + "毫秒");
    }
}

class NetMall{
    @Getter
    private String netMallName;

    public NetMall(String netMallName){
        this.netMallName = netMallName;
    }
    public double calcPrice(String productName){//更严谨的话用BigDecimal
        try{ TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }

        return ThreadLocalRandom.current().nextDouble() * 2 + productName.charAt(0);
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/824808.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

rt-thread-studio排除构建和添加构建

1、排除构建 排除构建后&#xff0c;此文件会直接在资源管理器里消失&#xff0c;但并没有删除源文件&#xff0c; 2、添加构建 先打开导航器 再在导航器里找到文件&#xff0c;选择添加构建

Meta AI 开源万物可分割 AI 模型(SAM)

开始 4 月 6 日&#xff0c;根据 Meta AI 官方博客&#xff0c;Meta AI 宣布推出了一个 AI 模型 Segment Anything Model&#xff08;SAM&#xff0c;分割一切模型&#xff09;。据介绍&#xff0c;该模型能够根据文本指令等方式实现图像分割&#xff0c;而且万物皆可识别和一…

java增函数的单变量求解后续,还好之前东西都留着,要不然还得从头来一遍

这是一篇本来不应该存在的程序&#xff0c;但是有了世界之大&#xff0c;就有这一段程序&#xff0c;如果你同情我的遭遇&#xff0c;那么进来帮我改进一下&#xff0c;大家一起交流讨论一下吧&#xff0c;我谢谢大家了。 背景 这个事情发生在五个月前的3月2日&#xff0c;详细…

HTML+CSS+JavaScript:实现B站评论发布效果

一、需求 1、用户输入内容&#xff0c;输入框左下角实时显示输入字数 2、为避免用户输入时在内容左右两端误按多余的空格&#xff0c;在发送评论时&#xff0c;检测用户输入的内容左右两端是否带有空格&#xff0c;若有空格&#xff0c;发布时自动取消左右两端的空格 3、若用…

一篇文章带你搞懂Java多态的概念、优点、实现多态的方式、以及不同方式的区别

一篇文章带你搞懂Java多态的概念、优点、使用场景 基本概念 ​ **多态&#xff08;Polymorphism&#xff09;是面向对象编程的一个重要特性&#xff0c;它指的是同一个行为具有多个不同表现形式或形态的能力。**它允许我们使用父类的引用变量来引用子类的对象&#xff0c;并根…

【MySQL】DQL语句

8&#xff0c;DQL 下面是黑马程序员展示试题库数据的页面 页面上展示的数据肯定是在数据库中的试题库表中进行存储&#xff0c;而我们需要将数据库中的数据查询出来并展示在页面给用户看。上图中的是最基本的查询效果&#xff0c;那么数据库其实是很多的&#xff0c;不可能在将…

antd form表单文本标签右对齐的

实现代码如下&#xff1a; const formItemLayout {labelCol: { // 表示当前label在整行的占比xs: { span: 12 },sm: { span: 9},},wrapperCol: { // 表示当前输入框在整行的占比xs: { span: 12 },sm: { span: 15 },}, };<Formname"advanced_search"className&q…

有哪些常见的微信小程序推广引流方法?

如何对小程序进行推广引流。只有如此&#xff0c;才能为小程序获取到更多忠实用户&#xff0c;使小程序实现更大的价值。今天就为大家介绍一下微信小程序常见有效的推广方式。 1、附近的小程序&#xff08;免费&#xff09; 在小程序后台&#xff0c;开通“附近的小程序”&am…

微服务实战项目-学成在线-选课学习(支付与学习中心)模块

微服务实战项目-学成在线-选课学习(支付与学习中心)模块 1 模块需求分析 1.1 模块介绍 本模块实现了学生选课、下单支付、学习的整体流程。 网站的课程有免费和收费两种&#xff0c;对于免费课程学生选课后可直接学习&#xff0c;对于收费课程学生需要下单且支付成功方可选…

(4)(4.4) 使用测试版和开发版

文章目录 4.4 使用测试版和开发版 4.4.1 测试版 4.4.2 最新开发版本 4.4.3 自定义固件构建服务器 4.4.4 固件的局限性 4.5 测试 4.4 使用测试版和开发版 4.4.1 测试版 在稳定版(Stable)发布之前&#xff0c;会发布测试版(Beta)。如果你想尝试较新的功能或帮助开发人员飞行…

HashMap 是怎么解决哈希冲突的

从三方面思考这个问题 1.要了解 Hash 冲突&#xff0c;那首先我们要先了解 Hash 算法和 Hash 表。&#xff08;如下图&#xff09; Hash 算法&#xff0c;就是把任意长度的输入&#xff0c;通过散列算法&#xff0c;变成固定长度的输出&#xff0c; 这个输出结果是散列值。 H…

学生护眼灯哪种好?分享眼科医生推荐的台灯

据统计&#xff0c;我国青少年近视率已位居世界第一&#xff0c;高中生和大学生的近视率均已超过68.8%且还在上升&#xff0c;就连小学生的近视率也接近40%&#xff01;繁重的学习任务是主要因素&#xff0c;再加上下课后手机平板等电子产品使用的影响下&#xff0c;近视可能性…

MySQL数据备份与还原

一、数据备份 1、使用mysqldump命令备份 mysqldump命令将数据库中的数据备份成一个文本文件。表的结构和表中的数据将存储在生成的文本文件中。 mysqldump命令的工作原理很简单。它先查出需要备份的表的结构&#xff0c;再在文本文件中生成一个CREATE语句。然后&#xff0c;将表…

redis的数据类型及操作

三、redis的数据类型 String字符串 set、get mset setex setnx 会检测键值对存不存在&#xff0c;如果存在不发生变化&#xff0c;如果存在则增加键值对 只增加 而set会覆盖原来的值 增加、修改 setrange 有下标则替换&#xff0c;没有则添加 getrange 获取全…

利用GRACE和地表质量模型计算地球弹性负荷变形实验笔记

1.背景和意义 由于地球的弹性结构&#xff0c;地球大尺度的质量迁移会导致地球产生负荷变形。地球的环境负载如大气、海洋、陆地水等的变化会使得固体地球产生明显的位移变化&#xff0c;为了准确研究有关地球物理信号&#xff0c;需要对弹性的负荷变形进行有效计算并扣除。通…

华云安参编的《云原生安全配置基线规范》正式发布

由中国信息通信研究院&#xff08;以下简称“中国信通院”&#xff09;、中国通信标准化协会主办的第十届可信云大会云原生安全分论坛于7月26日在北京国际会议中心成功召开。作为大会上展示的成果之一&#xff0c;由中国信通院联合行业领先企业共同编写的《云原生安全配置基线规…

项目管理:项目计划有哪些不可忽视的作用

为了确保项目在我们的预期范围内完成&#xff0c;编制计划是不可或缺的&#xff0c;它可以帮助项目管理团队进行提前思考、识别和管理任何疏漏和风险。 项目计划进行跟踪中有哪些不可忽视的作用&#xff1a; 1、了解成员的工作情况 分配任务后&#xff0c;项目经理应主动与…

STL string

文章目录 一、编码二、标准库中 string 类的使用1. 构造函数和拷贝构造函数2. 迭代器相关的成员函数3. 容量相关的成员函数4. 访问对象内容相关的成员函数5. 修改对象内容相关的成员函数6. 字符串操作相关的成员函数7. sting 类相关的非成员函数 三、vs 和 g 下 string 的结构四…

【Git】分支管理策略

文章目录 分支策略bug分支-master分支出现bug怎么办删除临时分⽀小结 分支策略 在实际开发中&#xff0c;我们应该按照⼏个基本原则进⾏分⽀管理&#xff1a; 1.master分⽀应该是⾮常稳定的&#xff0c;也就是仅⽤来发布新版本&#xff0c;平时不能在上⾯⼲活 2.⼲活都在dev…

echarts图表基本使用

折线图 import * as echarts from echarts;const chartDom document.getElementById(main); const myChart echarts.init(chartDom); const option {xAxis: {type: category,data: [Mon, Tue, Wed, Thu, Fri, Sat, Sun]},yAxis: {type: value},series: [{data: [820, 932, …