文章目录
1 介绍
CompletableFuture是自JDK1.8版本中引入的新的Future,常用于异步编程之中,所谓异步编程,简单来说就是:“程序运算与应用程序的主线程在不同的线程上完成,并且程序运算的线程能够向主线程通知其进度,以及成功失败与否的非阻塞式编码方式”,这句话听起来与前文中学习的ExecutorService提交异步执行任务并没有多大的区别,但是别忘了,无论是ExecutorService还是CompletionService,都需要主线程主动地获取异步任务执行的最终计算结果。
Google Guava所提供的ListenableFuture解决了这个问题,但是ListenableFuture无法将计算的结果进行异步任务的级联并行运算,甚至构成一个异步任务并行运算的pipeline,但是这一切在CompletableFuture中都得到了很好的支持。
关于Google Guava的Future可参考:Google Guava的Future
public class CompletableFuture<T> implements Future<T>, CompletionStage<T>
CompletableFuture实现了Future接口,所以具备Future的能力,但是还继承了CompletionStage接口
CompletionStage可以简单地认为,该接口是同步或者异步任务完成的某个阶段,它可以是整个任务管道中的最后一个阶段,甚至可以是管道中的某一个阶段,这就意味着可以将多个CompletionStage链接在一起形成一个异步任务链,前置任务执行结束之后会自动触发下一个阶段任务的执行。
CompletableFuture中包含了50多个方法,这一数字在JDK1.9版本中还得到了进一步的增加,这些方法可用于Future之间的组合、合并、任务的异步执行,多个Future的并行计算以及任务执行发生异常的错误处理等。
CompletableFuture的方法中,大多数入参都是函数式接口,比如Supplier、Function、BiFunction、Consumer等,因此熟练理解这些函数式接口是灵活使用CompletableFuture的前提和基础,同时CompletableFuture之所以能够异步执行任务,主要归功于其内部的ExecutorService,默认情况下为ForkJoinPool.commonPool()
2 CompletableFuture的快速入门
2.1 当作Future使用
CompletableFuture首先是一个Future,因此你可以将它当作普通的Future来使用,ExecutorService如果提交了Runnable类型的任务却又期望得到运算结果的返回,则需要在submit方法中将返回值的引用也作为参数传进去。
但是使用CompletableFuture就不需要返回值的引用也作为参数传进去。
public static void main(String[] args) {
// 定义Double类型的CompletableFuture
CompletableFuture<Double> completableFuture
= new CompletableFuture<>();
// 提交异步任务
Executors.newCachedThreadPool().submit(()->{
try {
TimeUnit.SECONDS.sleep(5);
// 设置结果
completableFuture.complete(Math.PI);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
// 非阻塞获取异步任务的计算结果,很明显,此刻异步任务并未执行结束,
// 那么可以采用默认值的方式(该方法也可以被认为是放弃异步任务的执行结果,但不会取消异步任务的执行)
// getNow: 如果任务还未结束,可以设置一个默认值,就会返回这个值,这里就是返回0
Double res = completableFuture.getNow(0d);
System.out.println("res == 0:" + res.equals(0d));
}
- 需要提前定义CompletableFuture
- 在普通的Future中,我们可以通过cancel操作来决定是否取消异步任务的继续执行,同样,在CompletableFuture中也有类似的操作。
public static void main(String[] args) {
// 定义Double类型的CompletableFuture
CompletableFuture<Double> completableFuture
= new CompletableFuture<>();
// 提交异步任务
Executors.newCachedThreadPool().submit(()->{
try {
TimeUnit.SECONDS.sleep(5);
// 取消
completableFuture.cancel(false);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
// get的时候就会抛出异常
Double res = null;
try {
res = completableFuture.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
2.2 任务的异步运行
CompletableFuture除了具备Future的基本特性之外,还可以直接使用它执行异步任务,通常情况下,任务的类型为Supplier和Runnable,前者非常类似于Callable接口,可返回指定类型的运算结果,后者则仍旧只是关注异步任务运行本身。
2.2.1 异步执行Supplier类型的任务
@FunctionalInterface
public interface Supplier<T> {
/**
* Gets a result.
* @return a result
*/
T get();
}
eg:可以直接调用CompletableFuture的静态方法supplyAsync异步执行Supplier类型的任务
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture<Double> future =
CompletableFuture.supplyAsync(() -> Math.PI);
System.out.println(future.get());
// supplyAsync方法的另外一个重载方法,允许传入ExecutorService
CompletableFuture<Double> future1 =
CompletableFuture.supplyAsync(() -> Math.PI, Executors.newCachedThreadPool());
System.out.println(future1.get());
}
2.2.2 异步执行Runnable类型的任务
也可以直接调用CompletableFuture的静态方法runAsync异步执行Runnable类型的任务
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("执行runable类型任务");
});
// 现在肯定没有完成,上面的任务需要执行3s
System.out.println("任务是否执行完成:" + future.isDone());
// 等待4s
TimeUnit.SECONDS.sleep(4);
System.out.println("任务是否执行完成:" + future.isDone());
}
任务是否执行完成:false
执行runable类型任务
任务是否执行完成:true
2.3 异步任务链
CompletableFuture还允许将执行的异步任务结果继续交由下一级任务来执行,下一级任务还可以有下一级,以此类推,这样就可以形成一个异步任务链或者任务pipeline。
2.3.1 thenApply: 以同步的方式继续处理上一个异步任务的结果
public <U> CompletableFuture<U> thenApply(
Function<? super T,? extends U> fn)
thenApply这个方法接收的是一个Function接口
eg:
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(3);
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + " is supplyAsync");
return "java";
}, executorService).thenApply((in -> {
System.out.println(Thread.currentThread().getName() + " is thenApply");
// 返回上一个任务返回字符串的长度
return in.length();
}));
// 获取结果
System.out.println(future.get());
}
上面这段代码就是一个简单的任务链,一共就是两个任务:
- supplyAsync的计算结果为"Java"
- thenApply继续处理"Java",返回字符串的长度
输出结果:可见supplyAsync与thenApply的任务执行是同一个线程,所以是同步执行任务链
pool-1-thread-1 is supplyAsync
pool-1-thread-1 is thenApply
4
2.3.2 thenApplyAsync:以异步的方式继续处理上一个异步任务的结果。
public CompletableFuture<Void> thenAccept(Consumer<? super T> action)
thenAccept接收的是一个Consumer函数接口
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(3);
CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + " is supplyAsync");
return "java";
}, executorService).thenAccept(in->{
System.out.println(Thread.currentThread().getName() + " is thenAccept");
System.out.println("长度是: " + in.length());
});
}
输出:可见supplyAsync与thenApply的任务执行不是是同一个线程,但是是在父线程处理的上一步的结果
pool-1-thread-1 is supplyAsync
main is thenAccept
长度是: 4
2.3.3 thenAcceptAsync:以异步的方式消费上一个异步任务的结果。
public CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action)
thenAcceptAsync同样接收的是一个Consumer函数接口
在任务链的末端,如果执行的任务既不想对上一个任务的输出做进一步的处理,又不想消费上一个任务的输出结果
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(3);
CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + " is supplyAsync");
return "java";
}, executorService).thenAcceptAsync(in->{
System.out.println(Thread.currentThread().getName() + " is thenAcceptAsync");
System.out.println("长度是: " + in.length());
});
}
pool-1-thread-1 is supplyAsync
ForkJoinPool.commonPool-worker-1 is thenAcceptAsync
长度是: 4
2.3.4 thenRun:以同步的方式执行Runnable任务。
在任务链的末端,如果执行的任务既不想对上一个任务的输出做进一步的处理,又不想消费上一个任务的输出结果,那么我们可以使用thenRun或者thenRunSync方法来执行Runnable任务。
public CompletableFuture<Void> thenRun(Runnable action) ;
接收的是一个Runnable接口,这个接口是没有形参的,所以就不可能吧上一个任务的结果传递进来,同样也没有返回值,所以也不会把自己的结果传递给下一个任务
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(3);
CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + " is supplyAsync");
return "java";
}, executorService).thenAcceptAsync(in->{
System.out.println(Thread.currentThread().getName() + " is thenAcceptAsync");
System.out.println("长度是: " + in.length());
}).thenRun(()->{
System.out.println(Thread.currentThread().getName() + " is thenRun");
System.out.println("All of task completed. " );
});
}
2.3.5 thenRunAsync:以异步的方式执行Runnable任务。
public CompletableFuture<Void> thenRunAsync(Runnable action)
// 可以指定自己的线程池
public CompletableFuture<Void> thenRunAsync(Runnable action,
Executor executor)
2.4 合并多个Future
CompletableFuture还允许将若干个Future合并成为一个Future的使用方式,可以通过thenCompose方法或者thenCombine方法来实现多个Future的合并。
2.4.1 thenCompose
public <U> CompletableFuture<U> thenCompose(
Function<? super T, ? extends CompletionStage<U>> fn
eg:
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 通过thenCompose将两个Future合并成一个Future
CompletableFuture<String> future = CompletableFuture
.supplyAsync(() -> "hello")
// s为上一个Future的计算结果, 比如这里就是hello
.thenCompose(s ->
// 再执行一个任务,逻辑就是如何合并上一个Future
CompletableFuture.supplyAsync(() -> s + " world")
);
String s = future.get();
System.out.println(s); // hello world
}
2.4.2 thenCombine
public static void main(String[] args) {
// 通过thenCombine将两个Future合并成一个Future
CompletableFuture<String> future
= CompletableFuture.supplyAsync(() -> "hello")
.thenCombine(CompletableFuture.supplyAsync(() -> " world"),
// s1为第一个Future的计算结果,s2为第二个Future的计算结果
(s1, s2) -> s1 + s2);
}
2.5 多Future的并行计算
如果想要多个独立的CompletableFuture同时并行执行,那么我们还可以借助于allOf()方法来完成,其有点类似于ExecutorService的invokeAll批量提交异步任务。
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 定义三个CompletableFuture
CompletableFuture<String> f1
= CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "Java";
});
CompletableFuture<String> f2
= CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(6);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "python";
});
CompletableFuture<String> f3
= CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(9);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "go";
});
// 批量并行执行,返回值是一个void类型的CompletableFuture
CompletableFuture<Void> future = CompletableFuture.allOf(f1, f2, f3).thenRun(() -> {
try {
System.out.println(f1.isDone() + " and result:" + f1.get());
System.out.println(f2.isDone() + " and result:" + f2.getNow("无"));
System.out.println(f3.isDone() + " and result:" + f3.getNow("无"));
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
});
// 阻塞等待运行结束
future.get();
}
如果只想运行一批Future中的一个任务,那么我们又该怎么办呢?只需要用anyOf方法替代allOf方法即可(这一点非常类似于ExecutorService的invokeAny方法),无论是allOf方法还是anyOf方法返回的CompletableFuture类型都是Void类型
2.6 错误处理
CompletableFuture对于异常的处理方式比普通的Future要优雅合理很多,它提供了handle方法,可用于接受上游任务计算过程中出现的异常错误,这样一来,我们便可以不用将错误的处理逻辑写在try…catch…语句块中了,更不需要只能通过Future的get方法调用才能得知异常错误的发生。
public static void main(String[] args) {
CompletableFuture.<String>supplyAsync(() -> {
throw new RuntimeException();
}).handle((t, e) -> {
if (e != null) {
// 发生了异常
System.out.println(Thread.currentThread().getName() + " 发生了异常");
return "Error";
}
return t;
}).thenAccept(System.out::println);
}
2.7 JDK 9对CompletableFuture的进一步支持
在JDK 9中,Doug Lea继续操刀Java并发包的开发,为CompletableFuture带来了更多新的改变,比如增加了新的静态工厂方法、实例方法,提供了任务处理的延迟和超时支持等。