Java 5中引入了Future接口,来表示异步计算的结果,但接口并没有任何其它方法来组合计算任务或处理错误。
Java 8 中引入了CompletableFuture类。该类实现了Future和CompletionStage接口。CompletionStage定义了异步计算的步骤,每个步骤可以进行组合。
CompletableFuture提供了50多个不同的方法,用来创建、组合、执行一步计算并且进行错误处理。
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
可以通过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();
大多数异步计算结果的处理都是通过提供一个回调函数。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();
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));
通过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。
当产生异常时,可以通过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
CompletableFuture类中的Api大多都有额外的以Async为后缀的方法。这些方法通常用来在另一个线程中运行相应的任务。没有Async后缀的方法执行下一阶段任务时在调用线程中执行。而Async方法在没传入Executor参数时是通过fork/join pool来执行任务。而带有Executor参数的方法在相应的参数中提供的线程池中执行。
http://www.baeldung.com/java-completablefuture