CompletableFuture多线程的使用

134 阅读7分钟

1.CompletableFuture创建异步任务

public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier){..}
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor){..}
public static CompletableFuture<Void> runAsync(Runnable runnable){..}
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor){..}
  • 两个 supplyAsync 方法是带返回值的;
  • 两个 runAsync 方法是不带返回值的;
  • Executor 线程池一般就使用我们自定义的线程池,效率更高一些;

执行异步任务的方式也很简单,只需要使用上述方法就可以了:

String str = "hello world";
CompletableFuture<String> futureA = CompletableFuture.supplyAsync(() -> {
  String s = str.toUpperCase();
  return s;
});

接下来看一下获取执行结果的几个方法:

V get();
V get(long timeout,Timeout unit);
T getNow(T defaultValue);
T join();

上面两个方法是 Future 中的实现方式, get() 会堵塞当前的线程,这就造成了一个问题,如果执行线程迟迟没有返回数据, get() 会一直等待下去,因此,第二个 get() 方法可以设置等待的时间. getNow() 方法比较有意思,表示当有了返回结果时会返回结果,如果异步线程抛了异常会返回自己设置的默认值.

2.CompletableFuture常用方法

2.1 thenAccept()

[入参是上一个任务的执行结果,任务 B 无返回值]

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

功能:无返回值,任务 A 执行完成以后返回的结果,作为 任务 B 的入参。 场景:执行任务 A,待任务 A 正常返回之后,用 A 的返回值作为执行任务 B 的入参,任务 B 无返回值. 示例如下:

CompletableFuture<String> futureA = CompletableFuture.supplyAsync(() -> "任务A");
CompletableFuture<Void> futureB = futureA.thenAccept(a -> {
    System.out.println("执行任务B.");
    System.out.println("参数:" + a); //参数:任务A
});

/// 输出结果
执行任务B.
参数:任务A

2.2 thenApply(..)

[入参是上一个任务的执行结果,任务 B 有返回值]

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<String> futureA = CompletableFuture.supplyAsync(() -> "hello");
CompletableFuture<String> futureB = futureA.thenApply(s->s + " world");
CompletableFuture<String> futureC = futureB.thenApply(String::toUpperCase);
System.out.println(futureC.join()); // join方法就是获取结果

/// 输出的结果
HELLO WORLD

2.3 thenRun(..)

[不关心任务 A 是否有返回值,在任务 A 执行结束之后,接着执行任务 B]

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

功能:任务A 执行完成之后,是不管任务 A 有没有返回值,此时都是接着去执行任务 B;任务B无返回值 场景:执行任务A,任务A执行完以后,执行任务B,任务B不接收任务A的返回值(不管A有没有返回值) 示例如下:

CompletableFuture<String> futureA = CompletableFuture.supplyAsync(() -> "任务A");
CompletableFuture<Void> futureB = futureA.thenRun(() -> System.out.println("执行任务B"));

/// 输出的结果
执行任务B

thenCombine(..) thenAcceptBoth(..) runAfterBoth(..)

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,? super U,? extends V> fn, Executor executor)

功能: 结合两个CompletionStage的结果,进行转化后返回 场景: 需要根据商品id查询商品的当前价格,分两步,查询商品的原始价格和折扣,这两个查询相互独立,当都查出来的时候用原始价格乘折扣,算出当前价格. 使用方法:thenCombine(..)

CompletableFuture<Double> futurePrice = CompletableFuture.supplyAsync(() -> 100d);
CompletableFuture<Double> futureDiscount = CompletableFuture.supplyAsync(() -> 0.8);
CompletableFuture<Double> futureResult = futurePrice.thenCombine(futureDiscount, (price, discount) -> price * discount);
System.out.println("最终价格为:" + futureResult.join()); //最终价格为:80.0

2.4 thenCompose(..)

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)

功能:这个方法接收的输入是当前的CompletableFuture的计算值,返回结果将是一个新的CompletableFuture; thenApply():它的功能相当于将CompletableFuture T 转换成CompletableFuture U,改变的是同CompletableFuture中的泛型类型 thenCompose():用来连接两个CompletableFuture,返回值是一个新的CompletableFuture;

CompletableFuture<String> futureA = CompletableFuture.supplyAsync(() -> "hello");
CompletableFuture<String> futureB = futureA.thenCompose(s -> CompletableFuture.supplyAsync(() -> s + "world"));
CompletableFuture<String> future3 = futureB.thenCompose(s -> CompletableFuture.supplyAsync(s::toUpperCase));
System.out.println(future3.join());

2.5 applyToEither(..) acceptEither(..) runAfterEither(..)

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

功能:执行两个CompletionStage的结果,那个先执行完了,就是用哪个的返回值进行下一步操作 场景:假设查询商品a,有两种方式,A和B,但是A和B的执行速度不一样,我们希望哪个先返回就用那个的返回值.

CompletableFuture<String> futureA = CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "通过方式A获取商品a";
        });
CompletableFuture<String> futureB = CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "通过方式B获取商品a";
        });
CompletableFuture<String> futureC = futureA.applyToEither(futureB, product -> "结果:" + product);
System.out.println(futureC.join()); //结果:通过方式A获取商品a

2.6 exceptionally(..)

public CompletionStage<T> exceptionally(Function<Throwable, ? extends T> fn);

功能:当运行出现异常时,调用该方法可进行一些补偿操作,如设置默认值. 场景:异步执行任务A获取结果,如果任务A执行过程中抛出异常,则使用默认值100返回.

CompletableFuture<String> futureA = CompletableFuture.
                supplyAsync(() -> "执行结果:" + (100 / 0))
                .thenApply(s -> "futureA result:" + s)
                .exceptionally(e -> {
                    System.out.println(e.getMessage()); //java.lang.ArithmeticException: / by zero
                    return "futureA result: 100";
                });
CompletableFuture<String> futureB = CompletableFuture.
                supplyAsync(() -> "执行结果:" + 50)
                .thenApply(s -> "futureB result:" + s)
                .exceptionally(e -> "futureB result: 100");
System.out.println(futureA.join());//futureA result: 100
System.out.println(futureB.join());//futureB result:执行结果:50

2.7 whenComplete(..)

public CompletionStage<T> whenComplete(BiConsumer<? super T, ? super Throwable> action);
public CompletionStage<T> whenCompleteAsync(BiConsumer<? super T, ? super Throwable> action);
public CompletionStage<T> whenCompleteAsync(BiConsumer<? super T, ? super Throwable> action,Executor executor);

功能:当CompletableFuture的计算结果完成,或者抛出异常的时候,都可以进入whenComplete方法执行,举个例子

CompletableFuture<String> futureA = CompletableFuture.
                supplyAsync(() -> "执行结果:" + (100 / 0))
                .thenApply(s -> "apply result:" + s)
                .whenComplete((s, e) -> {
                    if (s != null) {
                        System.out.println(s);//未执行
                    }
                    if (e == null) {
                        System.out.println(s);//未执行
                    } else {
                        System.out.println(e.getMessage());//java.lang.ArithmeticException: / by zero
                    }
                })
                .exceptionally(e -> {
                    System.out.println("ex"+e.getMessage()); //ex:java.lang.ArithmeticException: / by zero
             return "futureA result: 100"; }); 
System.out.println(futureA.join());//futureA result: 100

2.8 handle(..)

public <U> CompletionStage<U> handle(BiFunction<? super T, Throwable, ? extends U> fn);
public <U> CompletionStage<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn);
public <U> CompletionStage<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn,Executor executor);

功能:当CompletableFuture的计算结果完成,或者抛出异常的时候,可以通过handle方法对结果进行处理

CompletableFuture<String> futureA = CompletableFuture.
                supplyAsync(() -> "执行结果:" + (100 / 0))
                .thenApply(s -> "apply result:" + s)
                .exceptionally(e -> {
                    System.out.println("ex:" + e.getMessage()); //java.lang.ArithmeticException: / by zero
                    return "futureA result: 100";
                })
                .handle((s, e) -> {
                    if (e == null) {
                        System.out.println(s);//futureA result: 100
                    } else {
                        System.out.println(e.getMessage());//未执行
                    }
                    return "handle result:" + (s == null ? "500" : s);
                });
System.out.println(futureA.join());//handle result:futureA result: 100

通过控制台,我们可以看出,最后打印的是handle result:futureA result: 100,执行exceptionally后对异常进行了"美化",返回了默认值,那么handle得到的就是一个正常的返回,我们再试下,先调用handle再调用exceptionally的情况.

CompletableFuture<String> futureA = CompletableFuture.
                supplyAsync(() -> "执行结果:" + (100 / 0))
                .thenApply(s -> "apply result:" + s)
                .handle((s, e) -> {
                    if (e == null) {
                        System.out.println(s);//未执行
                    } else {
                        System.out.println(e.getMessage());//java.lang.ArithmeticException: / by zero
                    }
                    return "handle result:" + (s == null ? "500" : s);
                })
                .exceptionally(e -> {
                    System.out.println("ex:" + e.getMessage()); //未执行
                    return "futureA result: 100";
                });
System.out.println(futureA.join());//handle result:500

2.9 allOf(..) anyOf(..)

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

allOf: 当所有的CompletableFuture都执行完后执行计算 anyOf: 最快的那个CompletableFuture执行完之后执行计算 场景:查询一个商品详情,需要分别去查商品信息,卖家信息,库存信息,订单信息等,这些查询相互独立,在不同的服务上,假设每个查询都需要一到两秒钟,要求总体查询时间小于2秒.

   ExecutorService executorService = Executors.newFixedThreadPool(4);

    long start = System.currentTimeMillis();
    CompletableFuture<String> futureA = CompletableFuture.supplyAsync(() -> {
      try {
        Thread.sleep(1000 + RandomUtils.nextInt(1000));
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      return "商品详情";
    },executorService);

    CompletableFuture<String> futureB = CompletableFuture.supplyAsync(() -> {
      try {
        Thread.sleep(1000 + RandomUtils.nextInt(1000));
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      return "卖家信息";
    },executorService);

    CompletableFuture<String> futureC = CompletableFuture.supplyAsync(() -> {
      try {
        Thread.sleep(1000 + RandomUtils.nextInt(1000));
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      return "库存信息";
    },executorService);

    CompletableFuture<String> futureD = CompletableFuture.supplyAsync(() -> {
      try {
        Thread.sleep(1000 + RandomUtils.nextInt(1000));
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      return "订单信息";
    },executorService);

    CompletableFuture<Void> allFuture = CompletableFuture.allOf(futureA, futureB, futureC, futureD);
    allFuture.join();

    System.out.println(futureA.join() + futureB.join() + futureC.join() + futureD.join());
    System.out.println("总耗时:" + (System.currentTimeMillis() - start));

3.实际应用场景

3.1 业务代码异步调用

正常同步服务调用案例:

  @Test
  public void testSync(){
    System.out.println("同步创建订单开始");
    long start = System.currentTimeMillis();

    userService.queryInfo();
    orderService.queryOrder();
    accountService.queryAccount();

    long end = System.currentTimeMillis();
    System.out.println("创建订单结束,共花费:"+(end-start));
  }
同步创建订单开始
查看用户信息耗时1000ms
查看用户订单耗时1300ms
查看用户账户耗时1200ms
创建订单结束,共花费:3503

使用CompletableFuture异步调用案例:

  @Test
  public void testCompletableFuture(){
    System.out.println("异步创建订单开始");
    long start = System.currentTimeMillis();
    CompletableFuture<Void> userServiceFuture = CompletableFuture.runAsync(() -> {
      userService.queryInfo();
    });
    CompletableFuture<Void> orderServiceFuture = CompletableFuture.runAsync(() -> {
      orderService.queryOrder();
    });
    CompletableFuture<Void> accountServiceFuture = CompletableFuture.runAsync(() -> {
      accountService.queryAccount();
    });
    CompletableFuture.allOf(
        userServiceFuture,
        orderServiceFuture,
        accountServiceFuture).join();
    long end = System.currentTimeMillis();
    System.out.println("创建订单结束,共花费:"+(end-start));
  }
异步创建订单开始
查看用户信息耗时1000ms
查看用户账户耗时1200ms
查看用户订单耗时1300ms
创建订单结束,共花费:1306