Java 8 CompletableFuture的使用入门

1 前言

Java 5中引入了Future接口,来表示异步计算的结果,但接口并没有任何其它方法来组合计算任务或处理错误。

Java 8 中引入了CompletableFuture类。该类实现了Future和CompletionStage接口。CompletionStage定义了异步计算的步骤,每个步骤可以进行组合。

CompletableFuture提供了50多个不同的方法,用来创建、组合、执行一步计算并且进行错误处理。

2 将CompletableFuture作为Future使用

CompletableFuture实现了Future接口,除了可以作为Future使用,还包含一些额外的逻辑。

例如可以通过该类的无参构造器创建一个实例来描述结果,然后将实例传递给消费者并通过complete方法来完成计算。消费者可以使用get方法,该方法会阻塞,直到结果返回。

下面的例子,创建了一个CompletableFuture实例,随后在另一个线程中完成计算,同时立刻返回一个Future。

public Future calculateAsync() throws InterruptedException {
    CompletableFuture completableFuture
      = new CompletableFuture<>();

    Executors.newCachedThreadPool().submit(() -> {
        Thread.sleep(500);
        completableFuture.complete("Hello");
        return null;
    });

    return completableFuture;
}

Future<String> completableFuture = calculateAsync();
String result = completableFuture.get();
assertEquals("Hello", result);

有时还希望取消异步任务的执行。那么可以调用cancel方法。

public Future calculateAsyncWithCancellation() throws InterruptedException {
    CompletableFuture completableFuture = new CompletableFuture<>();

    Executors.newCachedThreadPool().submit(() -> {
        Thread.sleep(500);
        completableFuture.cancel(false);
        return null;
    });

    return completableFuture;
}

Future<String> future = calculateAsyncWithCancellation();
future.get(); // CancellationException

3 CompletedFuture执行异步计算逻辑

可以通过runAsync和supplyAsync分别创建由Runnable和Supplier函数接口构成的
CompletableFuture实例。这样可以完成自己的计算逻辑。

CompletableFuture future
  = CompletableFuture.supplyAsync(() -> "Hello");

// ...

assertEquals("Hello", future.get());

如果不需要获取返回值,调用runAsync。cFuture.get()返回类型为java.lang.Void

CompletableFuture cFuture = CompletableFuture.runAsync(() -> {
    System.out.println("runAsync");
});
cFuture.get();

4处理异步计算的结果

大多数异步计算结果的处理都是通过提供一个回调函数。thenApply方法也是通过
使用者提供的函数接口来处理结果。

CompletableFuture completableFuture
  = CompletableFuture.supplyAsync(() -> "Hello");

CompletableFuture<String> future = completableFuture
  .thenApply(s -> s + " World");

assertEquals("Hello World", future.get());

如果不需要返回值,那么提供一个Consumer函数接口。该方法接受一个参数,并返回void。如果即不想接收计算返回的结果,也不想返回最终的计算结果,仅希望执行一段任务,那么调用thenRun方法。

CompletableFuture completableFuture
  = CompletableFuture.supplyAsync(() -> "Hello");

CompletableFuture<Void> future = completableFuture
  .thenRun(() -> System.out.println("Computation finished."));

future.get();

5 组合Future

CompletableFuture Api能将CompletableFuture实例通过链式调用的形式组合在一起。下面的例子通过thenCompose方法将两个Future顺序地串联在一起。

CompletableFuture completableFuture
  = CompletableFuture.supplyAsync(() -> "Hello")
    .thenCompose(s -> CompletableFuture.supplyAsync(() -> s + " World"));

assertEquals("Hello World", completableFuture.get());

如果想执行两个独立的Future并对它们的结果进行处理,那么使用thenCombine方法,该方法接收一个Future与一个带有两个参数的Function,Function用来对结果进行处理。

CompletableFuture completableFuture
  = CompletableFuture.supplyAsync(() -> "Hello")
    .thenCombine(CompletableFuture.supplyAsync(
      () -> " World"), (s1, s2) -> s1 + s2));

assertEquals("Hello World", completableFuture.get());

如果不需要最终的返回结果,那么可以调用thenAcceptBoth方法。

CompletableFuture future = CompletableFuture.supplyAsync(() -> "Hello")
  .thenAcceptBoth(CompletableFuture.supplyAsync(() -> " World"),
    (s1, s2) -> System.out.println(s1 + s2));

6 并行运行多个Future

通过CompletableFuture.allOf可以并行执行多个任务,当所有任务都完成后get方法返回。

CompletableFuture future1 
  = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> future2 
  = CompletableFuture.supplyAsync(() -> "Beautiful");
CompletableFuture<String> future3 
  = CompletableFuture.supplyAsync(() -> "World");

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

// ...

combinedFuture.get();

assertTrue(future1.isDone());
assertTrue(future2.isDone());
assertTrue(future3.isDone());

combinedFuture.get()并不会返回所有结果最后的值,如果希望将所有的结果组合,可以通过CompletableFuture.join()方法来实现

String combined = Stream.of(future1, future2, future3)
  .map(CompletableFuture::join)
  .collect(Collectors.joining(" "));
  assertEquals("Hello Beautiful World", combined);

Future.join() 与get方法类似。因此可以通过Stream.map()方法对结果转换。此外当Future产生异常时会抛出一个unchecked exception。

7 异常处理

当产生异常时,可以通过handle方法来处理,该方法接受两个参数,异步计算
结果和异常。

String name = null;
// ...

CompletableFuture completableFuture 
  =  CompletableFuture.supplyAsync(() -> {
      if (name == null) {
          throw new RuntimeException("Computation error!");
      }
      return "Hello, " + name;
  })}).handle((s, t) -> s != null ? s : "Hello, Stranger!");

assertEquals("Hello, Stranger!", completableFuture.get());

有时还希望执行完成是以抛出异常的形式完成,那么可以通过completeExceptionally方法实现。当调用get方法后,会抛出一个异常。

CompletableFuture completableFuture = new CompletableFuture<>();
// ...
completableFuture.completeExceptionally(
  new RuntimeException("Calculation failed!"));
// ...
completableFuture.get(); // ExecutionException

8 异步方法

CompletableFuture类中的Api大多都有额外的以Async为后缀的方法。这些方法通常用来在另一个线程中运行相应的任务。没有Async后缀的方法执行下一阶段任务时在调用线程中执行。而Async方法在没传入Executor参数时是通过fork/join pool来执行任务。而带有Executor参数的方法在相应的参数中提供的线程池中执行。

9 原文地址

http://www.baeldung.com/java-completablefuture

你可能感兴趣的:(Java)