文章目录
- 面试
- CompletableFuture引入
- 使用CompletableFuture作为Future实现
- Async后缀方法
- 什么是ForkJoinPool
- 创建一个异步任务
- 计算完成时对结果的处理 whenComplete、exceptionally、handle
- 结果处理转换 thenApply
- 纯消费 thenAccept、thenRun、thenAcceptBoth、runAfterBoth
- 组合 thenCompose、thenCombine
- 任意一个方法执行完成就结束acceptEither、applyToEither
- 辅助方法allOf、anyOf
- 使用CompletableFuture提升程序性能
- CompletableFuture使用详解
- 参考
面试
CompletableFuture使用了空间换时间的思路,异步查询也可以使用FutureTask。
但是使用Future有个问题,就是在于返回获取异步结果的时候需要有等待状态,这个等待的状态是需要消耗时间进行堵塞的。
CompletableFuture只有在最后汇总的时候才进行堵塞,这时所有任务都已经完成了。
通过线程池性能稳定,也可以获取执行结果,并捕获异常。但是,在业务复杂的情况下,一个异步调用可能会依赖于另一个异步调用的执行结果。
CompletableFuture引入
Future是JDK5添加的类,用来描述一个异步计算的结果。可以使用isDone
方法检查计算是否完成,或者使用get
阻塞住调用线程,直到计算完成返回结果,也可以使用cancel
方法停止任务的执行。
public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newFixedThreadPool(10);
Future<Integer> f = executorService.submit(() ->{
// 长时间的异步计算
// 然后返回结果
System.out.println("A");
return 100;
});
// while(!f.isDone()){
// System.out.println("D");
// }
System.out.println("B");
Integer i = f.get();
System.out.println("C" + i);
executorService.shutdown();
return;
}
结果:
B
A
C100
虽然Future以及相关使用方法提供了异步执行任务的能力,但是对于结果的获取却是很不方便,只能通过阻塞或者轮询的方式得到任务的结果
。阻塞的方式显然和异步编程的初衷相违背,轮询的方式又会耗费无谓的CPU资源,而且也不能及时地得到计算结果,所以可以用观察者设计模式,当计算结果完成及时通知监听者。
在JDK8里面,就新增了CompletableFuture类,提供了非常强大的Future的扩展功能,可以帮助我们简化异步编程的复杂性,提供了函数式编程的能力
,可以通过回调的方式处理计算结果
,并且提供了转换和组合CompletableFuture的方法
。
CompletableFuture实现了Future、CompletionStage接口。CompletionStage接口定义了可与其他Future组合成异步计算契约。
使用CompletableFuture作为Future实现
public class CompletableFuture<T> implements Future<T>, CompletionStage<T>
CompletableFuture类实现Future接口,因此可以将其用作Future实现,但需要额外的完成实现逻辑。
例如:可以使用无构参构造函数创建此类的实例,然后使用complete方法完成。消费者可以使用get方法来阻塞当前线程,直到get()结果。
在下面的示例中,有一个创建CompletableFuture实例的方法,然后在另一个线程中计算并立即返回Future。
计算完成后,该方法通过将结果提供给完整方法来完成Future:
public static void main(String[] args) throws Exception{
Future<String> future = calculateAsync();
System.out.println(Thread.currentThread().getName() + " main");
System.out.println(future.get());
}
public static Future<String> calculateAsync() {
CompletableFuture<String> completableFuture = new CompletableFuture<>();
Executors.newCachedThreadPool().execute(() -> {
System.out.println(Thread.currentThread().getName() + " execute");
String result = "Hello " + " World";
completableFuture.complete(result);
});
return completableFuture;
}
结果:
main main
pool-1-thread-1 execute
Hello World
Async后缀方法
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类中的API的大多数方法都有两个带有Async后缀的附加修饰。这些方法表示用于异步线程。
- 没有Async后缀的方法使用调用线程运行下一个执行线程阶段。
- 带有Async后缀的方法默认使用
ForkJoinPool.commonPool()
线程池的fork/join
实现运算任务。 - 带有Async后缀的方法并且传递
Executor
对象,由Executor
任务去运行。
什么是ForkJoinPool
ForkJoinPool是ExecutorSerice的一个补充,而不是替代品。
JAVA8中CompeleteFuture、并发流等都是基于ForkJoinPool实现,默认并发数是CPU核数。
创建一个异步任务
CompletableFuture.completedFuture
是一个静态辅助方法,用来返回一个已经计算好的CompletableFuture
。
public static <U> CompletableFuture<U> completedFuture(U value)
Async结尾的方法都是可以异步执行的,如果指定了线程池,会在指定的线程池中执行,如果没有指定,默认会在ForkJoinPool.commonPool()
中执行。
- runAsync方法:它以
Runnabel
函数式接口类型为参数,所以CompletableFuture的计算结果为空。 - supplyAsync方法:以
Supplier<U>
函数式接口类型为参数,所以CompletableFuture有返回值,且计算结果类型为U。
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 executor)
CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> System.out.println("runAsync"));
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "supplyAsync");
System.out.println(future1.get());
System.out.println(future2.get());
结果:
runAsync
null
supplyAsync
计算完成时对结果的处理 whenComplete、exceptionally、handle
当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)
参数类型为 BiConsumer<? super T, ? super Throwable>
会获取上一步计算的计算结果和异常信息。
以Async结尾的方法可能会使用其它的线程去执行,如果使用相同的线程池,也可能会被同一个线程选中执行。
public class ThreadUtil {
public static void sleep(long ms) {
try {
Thread.sleep(ms);
} catch (InterruptedException e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage());
}
}
}
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
ThreadUtil.sleep(100);
return 20;
}).whenCompleteAsync((v, e) -> {
System.out.println("VV:" + v);
System.out.println("EE:" + e);
});
System.out.println("A");
System.out.println(future.get());
System.out.println("B");
结果:
A
VV:20
EE:null
20
B
public CompletableFuture<T> exceptionally(Function<Throwable,? extends T> fn)
该方法是对异常情况的处理,当函数异常时执行。
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
ThreadUtil.sleep(100);
return 20/0;
}).whenCompleteAsync((v, e) -> {
System.out.println("VV:" + v);
System.out.println("EE:" + e);
}).exceptionally((e)->{
System.out.println(e.getMessage());
return 30;
});
System.out.println("A");
System.out.println(future.get());
System.out.println("B");
结果:
A
VV:null
EE:java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
java.lang.ArithmeticException: / by zero
30
B
public <U> CompletableFuture<U> handle(BiFunction<? super T,Throwable,? extends U> fn)
public <U> CompletableFuture<U> handleAsync(BiFunction<? super T,Throwable,? extends U> fn)
public <U> CompletableFuture<U> handleAsync(BiFunction<? super T,Throwable,? extends U> fn, Executor executor)
handle 方法和whenComplete方法类似,只不过接收的是一个 BiFunction<? super T,Throwable,? extends U> fn
类型的参数,因此有 whenComplete
方法和 转换的功能 (thenApply
)。
以Async结尾的方法可能会使用其它的线程去执行,如果使用相同的线程池,也可能会被同一个线程选中执行。
CompletableFuture<Integer> future = CompletableFuture
.supplyAsync(() -> {
ThreadUtil.sleep(100);
return 20;
})
.handle((t, e)->{
System.out.println("TT:" + t);
System.out.println("EE:" + e);
return 10;
});
System.out.println("A");
System.out.println(future.get());
System.out.println("B");
结果:
A
TT:20
EE:null
10
B
// 异常情况
CompletableFuture<Integer> future = CompletableFuture
.supplyAsync(() -> {
ThreadUtil.sleep(100);
return 20/0;
})
.handle((t, e)->{
System.out.println("TT:" + t);
System.out.println("EE:" + e);
return 10;
});
System.out.println("A");
System.out.println(future.get());
System.out.println("B");
结果:
A
TT:null
EE:java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
10
B
结果处理转换 thenApply
当前阶段正常完成以后执行,而且当前阶段的执行的结果会作为下一阶段的输入参数。
thenApplyAsync默认是异步执行的。这里所谓的异步指的是不在当前线程内执行。
thenApply相当于回调函数(callback)。
CompletableFuture 由于有回调,可以不必等待一个计算完成而阻塞着调用线程,可以在一个结果计算完成之后紧接着执行某个Action。我们可以将这些操作串联起来。
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<Integer> future = CompletableFuture
.supplyAsync(() -> {
ThreadUtil.sleep(100);
return 20;
})
.thenApply((f1)->{
System.out.println("F1:" + f1);
return f1 * 2;
})
.thenApply((f2)->{
System.out.println("F2:" + f2);
return f2 * 2;
})
.thenApply((f3)->{
System.out.println("F3:" + f3);
return f3 * 2;
});
System.out.println("A");
System.out.println("future:" + future.get());
System.out.println("B");
结果:
A
F1:20
F2:40
F3:80
future:160
B
这些方法不是马上执行的,也不会阻塞,而是前一个执行完成后继续执行下一个。
和 handle 方法的区别是,handle 会处理正常计算值和异常,不会抛出异常。而 thenApply 只会处理正常计算值,有异常则抛出。
纯消费 thenAccept、thenRun、thenAcceptBoth、runAfterBoth
thenAccept和thenRun都是无返回值的。如果说thenApply是不停的输入输出的进行生产,那么thenAccept和thenRun就是在进行消耗。它们是整个计算的最后两个阶段。
- thenAccept接收上一阶段的输出作为本阶段的输入
- thenRun根本不关心前一阶段的输出,根本不不关心前一阶段的计算结果,因为它不需要输入参数
单纯的去消费结果而不会返回新的值,因些计算结果为 Void。
public CompletableFuture<Void> thenAccept(Consumer<? super T> action)
public CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action)
public CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action, Executor executor)
CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> {
ThreadUtil.sleep(100);
return 20;
}).thenAccept((c)->{
System.out.println("CC:" + c);
}).thenAcceptAsync((c2)->{
System.out.println("C2:" + c2);
});
System.out.println("A");
System.out.println(future.get());
System.out.println("B");
结果:
A
CC:20
C2:null
null
B
public <U> CompletableFuture<Void> thenAcceptBoth(CompletionStage<? extends U> other, BiConsumer<? super T,? super U> action)
public <U> CompletableFuture<Void> thenAcceptBothAsync(CompletionStage<? extends U> other, BiConsumer<? super T,? super U> action)
public <U> CompletableFuture<Void> thenAcceptBothAsync(CompletionStage<? extends U> other, BiConsumer<? super T,? super U> action, Executor executor)
和 thenAccept 相比,参数类型多了一个CompletionStage<? extends U> other
,BiConsumer第一个参数接收第一个supplyAsync返回值,第二个参数接收CompletionStage返回值。
CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName());
ThreadUtil.sleep(100);
return 20;
}).thenAcceptBoth(CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName());
return 2;
}), (a, b)->{
System.out.println(Thread.currentThread().getName() + "-AA:" + a);
System.out.println(Thread.currentThread().getName() + "-BB:" + b);
});
System.out.println("A");
System.out.println(future.get());
System.out.println("B");
结果:
ForkJoinPool.commonPool-worker-1
ForkJoinPool.commonPool-worker-2
A
ForkJoinPool.commonPool-worker-1-AA:20
ForkJoinPool.commonPool-worker-1-BB:2
null
B
public CompletableFuture<Void> runAfterBoth(CompletionStage<?> other, Runnable action)
public CompletableFuture<Void> runAfterBothAsync(CompletionStage<?> other, Runnable action)
public CompletableFuture<Void> runAfterBothAsync(CompletionStage<?> other, Runnable action, Executor executor)
runAfterBoth 和以上方法不同,传一个 Runnable 类型的参数,不接收上一级的返回值
更彻底的:public CompletableFuture<Void> thenRun(Runnable action)
public CompletableFuture<Void> thenRunAsync(Runnable action)
public CompletableFuture<Void> thenRunAsync(Runnable action, Executor executor)
以上是彻底的纯消费,完全忽略计算结果。
组合 thenCompose、thenCombine
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)
以上接收类型为 Function<? super T,? extends CompletionStage<U>> fn
,fn 接收上一级返回的结果,并返回一个新的 CompletableFuture。
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + " A");
ThreadUtil.sleep(100);
return 20;
}).thenApply((f)->{
System.out.println(Thread.currentThread().getName() + " B");
return f + 10;
}).thenCompose((s)->{
System.out.println(Thread.currentThread().getName() + " SS:" + s);
return CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + " C");
return s * 5;
});
});
System.out.println("A");
System.out.println(future.get());
System.out.println("B");
结果:
ForkJoinPool.commonPool-worker-1 A
A
ForkJoinPool.commonPool-worker-1 B
ForkJoinPool.commonPool-worker-1 SS:30
ForkJoinPool.commonPool-worker-2 C
150
B
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是并行执行的,它们之间并没有先后依赖顺序,other并不会等待先前的CompletableFuture执行完毕后再执行。
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
ThreadUtil.sleep(1000);
System.out.println(Thread.currentThread().getName());
return 20;
}).thenApply((f)->{
ThreadUtil.sleep(1000);
System.out.println(Thread.currentThread().getName());
return f + 10;
}).thenCombine(CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + " thenCombineAsync");
return 10;
}), (x, y)->{
System.out.println(Thread.currentThread().getName() + " XX:" + x);
System.out.println(Thread.currentThread().getName() + " YY:" + y);
return x + y;
});
System.out.println("A");
System.out.println(future.get());
System.out.println("B");
结果:
ForkJoinPool.commonPool-worker-2 thenCombineAsync
A
ForkJoinPool.commonPool-worker-1
ForkJoinPool.commonPool-worker-1
ForkJoinPool.commonPool-worker-1 XX:30
ForkJoinPool.commonPool-worker-1 YY:10
40
B
thenCombine
和 supplyAsync
不一定哪个先哪个后,是并行执行的。
任意一个方法执行完成就结束acceptEither、applyToEither
public CompletableFuture<Void> acceptEither(CompletionStage<? extends T> other, Consumer<? super T> action)
public CompletableFuture<Void> acceptEitherAsync(CompletionStage<? extends T> other, Consumer<? super T> action)
public CompletableFuture<Void> acceptEitherAsync(CompletionStage<? extends T> other, Consumer<? super T> action, Executor executor)
Random random = new Random();
CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> {
ThreadUtil.sleep(random.nextInt(1000));
return "A";
}).acceptEither(CompletableFuture.supplyAsync(() -> {
ThreadUtil.sleep(random.nextInt(1000));
return "B";
}), (c) -> {
System.out.println(c);
});
System.out.println(future.get());
结果:
B
null
或者:
A
null
以上代码有时输出A,有时输出B,哪个Future先执行完就会根据它的结果计算。
acceptEither方法是当任意一个 CompletionStage 完成的时候,action 这个消费者就会被执行。这个方法返回 CompletableFuture<Void>
。
public <U> CompletableFuture<U> applyToEither(CompletionStage<? extends T> other, Function<? super T,U> fn)
public <U> CompletableFuture<U> applyToEitherAsync(CompletionStage<? extends T> other, Function<? super T,U> fn)
public <U> CompletableFuture<U> applyToEitherAsync(CompletionStage<? extends T> other, Function<? super T,U> fn, Executor executor)
applyToEither 方法是当任意一个 CompletionStage 完成的时候,fn会被执行,它的返回值会当作新的CompletableFuture<U>
的计算结果。
Random random = new Random();
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
ThreadUtil.sleep(random.nextInt(1000));
return "A";
}).applyToEither(CompletableFuture.supplyAsync(() -> {
ThreadUtil.sleep(random.nextInt(1000));
return "B";
}), (c) -> {
System.out.println(c);
return c + "DD";
});
System.out.println(future.get());
结果:
B
BDD
或者:
A
ADD
acceptEither 和 applyToEither 区别是: acceptEither 没有返回值,applyToEither 有返回值。
辅助方法allOf、anyOf
public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs)
当所有的CompletableFuture都执行完后才往下执行,没有返回值。public static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs)
当任意一个CompletableFuture执行完后就会执行计算,计算的结果相同,返回一个Object类型的值。
使用CompletableFuture提升程序性能
如果每个操作都很简单的话,没有必要用这种多线程异步的方式,因为创建线程还需要时间,还不如直接同步执行来得快。
只有当每个操作很复杂需要花费相对很长的时间(比如,调用多个其它的系统的接口;比如,商品详情页面这种需要从多个系统中查数据显示的)的时候用CompletableFuture才合适,不然区别真的不大,还不如顺序同步执行。
定义一个对象:
@Data
public class UserInfo {
private Integer id;
private String name;
private Integer jobId;
private String jobDes;
private Integer carId;
private String carDes;
private Integer homeId;
private String homeDes;
}
这个对象里面的homeid,jobid,carid都是用于匹配对应的住房信息描述,职业信息描述,购车信息描述。
对于将id转换为描述信息的方式需要通过额外的sql查询,这里做了个简单的工具类来进行模拟:
import java.util.concurrent.TimeUnit;
public class QueryUtils {
public String queryCar(Integer carId){
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "car_desc";
}
public String queryJob(Integer jobId){
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "job_desc";
}
public String queryHome(Integer homeId){
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "home_desc";
}
}
假设每次查询需要消耗1s,那么遍历的一个size为n的集合查询消耗的时间就是n * 3s。
下面来使用CompletableFuture提升性能:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Collectors;
public class QueryUserService {
private Supplier<QueryUtils> queryUtilsSupplier = new Supplier<QueryUtils>() {
@Override
public QueryUtils get() {
return new QueryUtils();
}
};
// 传统方式
public UserInfo converUserInfo(UserInfo userInfo) {
userInfo.setCarDes(queryUtilsSupplier.get().queryCar(userInfo.getCarId()));
userInfo.setHomeDes(queryUtilsSupplier.get().queryHome(userInfo.getHomeId()));
userInfo.setJobDes(queryUtilsSupplier.get().queryJob(userInfo.getJobId()));
return userInfo;
}
// CompletableFuture方式
public UserInfo converUserInfoForFuture(UserInfo userInfo) {
CompletableFuture<String> getCarDesc = CompletableFuture.supplyAsync(() -> queryUtilsSupplier.get().queryCar(userInfo.getCarId()));
getCarDesc.thenAccept((carDesc) -> userInfo.setCarDes(carDesc));
CompletableFuture<String> getHomeDesc = CompletableFuture.supplyAsync(() -> queryUtilsSupplier.get().queryHome(userInfo.getHomeId()));
getHomeDesc.thenAccept((homeDesc) -> userInfo.setHomeDes(homeDesc));
CompletableFuture<String> getJobDesc = CompletableFuture.supplyAsync(() -> queryUtilsSupplier.get().queryJob(userInfo.getJobId()));
getJobDesc.thenAccept((jobDesc) -> userInfo.setJobDes(jobDesc));
CompletableFuture<Void> getUserInfo = CompletableFuture.allOf(getCarDesc, getHomeDesc, getJobDesc);
getUserInfo.thenAccept(new Consumer<Void>() {
@Override
public void accept(Void result) {
System.out.println("全部完成查询");
}
});
getUserInfo.join();
return userInfo;
}
public static void main(String[] args) {
long begin = System.currentTimeMillis();
// 多线程环境需要注意线程安全问题
List<UserInfo> userInfoList = Collections.synchronizedList(new ArrayList<>());
for (int i = 0; i <= 20; i++) {
UserInfo userInfo = new UserInfo();
userInfo.setId(i);
userInfo.setName("username" + i);
userInfo.setCarId(i);
userInfo.setJobId(i);
userInfo.setHomeId(i);
userInfoList.add(userInfo);
}
QueryUserService queryUserService = new QueryUserService();
//stream 查询一个用户花费3s 并行计算后一个用户1秒左右 查询21个用户花费21秒
userInfoList.stream().map(userInfo -> {
userInfo = queryUserService.converUserInfoForFuture(userInfo);
return userInfo;
}).collect(Collectors.toList());
System.out.println("=============");
long end = System.currentTimeMillis();
System.out.println(end - begin);
}
}
CompletableFuture使用详解
static方法说明
CompletableFuture的几个 static 方法,它们可以实例化一个 CompletableFuture 实例。
public static <U> CompletableFuture<U> completedFuture(U value)
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 executor)
completedFuture
返回一个已经计算好的CompletableFuture
。runAsync
方法接收的是Runnable
的实例,但是它没有返回值。supplyAsync
方法是JDK8函数式接口,无参数,会返回一个结果。- 这两个方法是
executor
的升级,表示让任务在指定的线程池中执行,不指定的话,通常任务是在ForkJoinPool.commonPool()
线程池中执行的。
supplyAsync()使用
静态方法runAsync
和supplyAsync
允许我们相应地从Runnable
和Supplier
功能类型中创建CompletableFuture
实例。
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + " A");
return "Hello";
});
System.out.println(Thread.currentThread().getName() + " B");
System.out.println(future.get());
结果:
main B
ForkJoinPool.commonPool-worker-1 A
Hello
thenRun()使用
在两个任务任务A,任务B中,如果既不需要任务A的值也不想在任务B中引用,那么你可以将Runnable lambda 传递给thenRun()方法。
在下面的示例中,在调用future.get()方法之后,我们只需在控制台中打印一行:
模板
CompletableFuture.runAsync(() -> {}).thenRun(() -> {});
CompletableFuture.supplyAsync(() -> "resultA").thenRun(() -> {});
- 第一行用的是
thenRun(Runnable runnable)
,任务 A 执行完执行 B,并且 B 不需要 A 的结果。 - 第二行用的是
supplyAsync(Supplier<U> supplier)
,任务 A 执行完执行 B,会返回resultA,但是 B 不需要 A 的结果。
实战
CompletableFuture<Void> completableFuture = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + " A");
return "Hello";
}).thenRun(() -> {
System.out.println(Thread.currentThread().getName() + " Computation finished.");
});
System.out.println(Thread.currentThread().getName() + " B");
System.out.println(completableFuture.get());
结果:
ForkJoinPool.commonPool-worker-1 A
main Computation finished.
main B
null
thenAccept()使用
在两个任务任务A,任务B中,如果你不需要在Future中有返回值,则可以用 thenAccept方法接收将计算结果传递给它。最后的future.get()调用返回Void类型的实例。
模板
CompletableFuture.runAsync(() -> {}).thenAccept(resultA -> {});
CompletableFuture.supplyAsync(() -> "resultA").thenAccept(resultA -> {});
- 第一行中,runAsync不会有返回值,第二个方法thenAccept,接收到的resultA值为null,同时任务B也不会有返回结果。
- 第二行中,supplyAsync有返回值,同时任务B不会有返回结果。
实战
CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + " A");
return "Hello";
});
CompletableFuture<Void> future = completableFuture.thenAccept(s -> {
System.out.println(Thread.currentThread().getName() + " Computation returned: " + s);
});
System.out.println(Thread.currentThread().getName() + " B");
System.out.println(future.get());
结果:
ForkJoinPool.commonPool-worker-1 A
main Computation returned: Hello
main B
null
thenApply()使用
在两个任务任务A,任务B中,任务B想要任务A计算的结果,可以用thenApply方法来接受一个函数实例,用它来处理结果,并返回一个Future函数的返回值:
模板
CompletableFuture.runAsync(() -> {}).thenApply(resultA -> "resultB");
CompletableFuture.supplyAsync(() -> "resultA").thenApply(resultA -> resultA + " resultB");
- 第二行用的是
thenApply(Function fn)
,任务 A 执行完执行 B,B 需要 A 的结果,同时任务 B 有返回值。
实战
CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
ThreadUtil.sleep(1000L);
System.out.println(Thread.currentThread().getName() + " A");
return "Hello";
});
CompletableFuture<String> future = completableFuture.thenApply(s -> {
System.out.println(Thread.currentThread().getName() + " B");
return s + " World";
});
System.out.println(Thread.currentThread().getName() + " C");
System.out.println(future.get());
结果:
main C
ForkJoinPool.commonPool-worker-1 A
ForkJoinPool.commonPool-worker-1 B
Hello World
当然,多个任务的情况下,如果任务 B 后面还有任务 C,往下继续调用 .thenXxx()
即可。
thenCompose()使用
CompletableFuture API 的最佳场景是能够在一系列计算步骤中组合CompletableFuture实例。
这种组合结果本身就是CompletableFuture,允许进一步再续组合。这种方法在函数式语言中无处不在,通常被称为monadic
设计模式。
简单说,Monad
就是一种设计模式,表示将一个运算过程,通过函数拆解成互相连接的多个步骤。你只要提供下一步运算所需的函数,整个运算就会自动进行下去。
在下面的示例中,我们使用thenCompose方法按顺序组合两个Futures。
请注意,此方法采用返回CompletableFuture实例的函数。该函数的参数是先前计算步骤的结果。这允许我们在下一个CompletableFuture的lambda中使用这个值:
CompletableFuture<String> completableFuture = CompletableFuture
.supplyAsync(() -> {
ThreadUtil.sleep(1000L);
System.out.println(Thread.currentThread().getName() + " A");
return "Hello";
})
.thenCompose(s -> CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + " B");
return s + " World";
}));
System.out.println(Thread.currentThread().getName() + " C");
System.out.println(completableFuture.get());
结果:
main C
ForkJoinPool.commonPool-worker-1 A
ForkJoinPool.commonPool-worker-1 B
Hello World
该thenCompose方法连同thenApply一样实现了结果的合并计算。但是他们的内部形式是不一样的,它们与Java 8中可用的Stream和Optional类的map和flatMap方法是有着类似的设计思路在里面的。
两个方法都接收一个CompletableFuture并将其应用于计算结果,但thenCompose(flatMap)方法接收一个函数,该函数返回相同类型的另一个CompletableFuture对象。此功能结构允许将这些类的实例继续进行组合计算。
thenCombine()、thenAcceptBoth()使用
取两个任务的结果。
如果要执行两个独立的任务,并对其结果执行某些操作,可以用Future的thenCombine方法。
模板
CompletableFuture<String> cfA = CompletableFuture.supplyAsync(() -> "resultA");
CompletableFuture<String> cfB = CompletableFuture.supplyAsync(() -> "resultB");
cfA.thenAcceptBoth(cfB, (resultA, resultB) -> {});
cfA.thenCombine(cfB, (resultA, resultB) -> "result A + B");
实战
CompletableFuture<String> completableFuture = CompletableFuture
.supplyAsync(() -> {
ThreadUtil.sleep(1000L);
System.out.println(Thread.currentThread().getName() + " A");
return "Hello";
})
.thenCombine(CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + " B");
return " World";
}), (s1, s2) -> {
System.out.println(Thread.currentThread().getName() + " C");
return s1 + s2;
});
System.out.println(Thread.currentThread().getName() + " D");
System.out.println(completableFuture.get());
结果:
ForkJoinPool.commonPool-worker-2 B
main D
ForkJoinPool.commonPool-worker-1 A
ForkJoinPool.commonPool-worker-1 C
Hello World
更简单的情况是,当你想要使用两个Future结果时,但不需要将任何结果值进行返回时,可以用thenAcceptBoth
,它表示后续的处理不需要返回值,而 thenCombine
表示需要返回值:
CompletableFuture<Void> completableFuture = CompletableFuture
.supplyAsync(() -> {
ThreadUtil.sleep(1000L);
System.out.println(Thread.currentThread().getName() + " A");
return "Hello";
})
.thenAcceptBoth(CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + " B");
return " World";
}), (s1, s2) -> {
System.out.println(Thread.currentThread().getName() + " C," + s1 + s2);
});
System.out.println(Thread.currentThread().getName() + " D");
System.out.println(completableFuture.get());
结果:
ForkJoinPool.commonPool-worker-2 B
main D
ForkJoinPool.commonPool-worker-1 A
ForkJoinPool.commonPool-worker-1 C,Hello World
null
thenApply()和thenCompose()之间的区别
在前面的部分中,我们展示了关于thenApply()和thenCompose()的示例。这两个API都是使用的CompletableFuture调用,但这两个API的使用是不同的。
thenApply()
此方法用于处理先前调用的结果。但是,要记住的一个关键点是返回类型是转换泛型中的类型,是同一个CompletableFuture。
因此,当我们想要转换CompletableFuture 调用的结果时,效果是这样的 :
CompletableFuture<Integer> finalResult = compute().thenApply(s-> s + 1);
thenCompose()
该thenCompose()方法类似于thenApply()在都返回一个新的计算结果。但是,thenCompose()使用前一个Future作为参数。它会直接使结果变新的Future,而不是我们在thenApply()中到的嵌套Future,而是用来连接两个CompletableFuture,是生成一个新的CompletableFuture:
CompletableFuture<Integer> computeAnother(Integer i){
return CompletableFuture.supplyAsync(() -> 10 + i);
}
CompletableFuture<Integer> finalResult = compute().thenCompose(this::computeAnother);
因此,如果想要继续嵌套链接CompletableFuture 方法,那么最好使用thenCompose()。
并行运行多个任务
当我们需要并行执行多个任务时,我们通常希望等待所有它们执行,然后处理它们的组合结果。
该CompletableFuture.allOf
静态方法允许等待所有的完成任务:
API
public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs){...}
实战
CompletableFuture<String> 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());
请注意,CompletableFuture.allOf()
的返回类型是CompletableFuture <Void>
。这种方法的局限性在于它不会返回所有任务的综合结果。相反,你必须手动从Futures
获取结果。幸运的是,CompletableFuture.join()
方法和Java 8 Streams API可以解决:
String combined = Stream.of(future1, future2, future3)
.map(CompletableFuture::join)
.collect(Collectors.joining(" "));
assertEquals("Hello Beautiful World", combined);
CompletableFuture 提供了 join()
方法,它的功能和 get() 方法是一样的,都是阻塞获取值,它们的区别在于 join()
抛出的是 unchecked Exception。这使得它可以在Stream.map()
方法中用作方法引用。
异常处理
说到这里,我们顺便来说下 CompletableFuture 的异常处理。这里我们要介绍两个方法:
public CompletableFuture<T> exceptionally(Function<Throwable, ? extends T> fn);
public <U> CompletionStage<U> handle(BiFunction<? super T, Throwable, ? extends U> fn);
看下代码
CompletableFuture.supplyAsync(() -> "resultA")
.thenApply(resultA -> resultA + " resultB")
.thenApply(resultB -> resultB + " resultC")
.thenApply(resultC -> resultC + " resultD");
上面的代码中,任务 A、B、C、D 依次执行,如果任务 A 抛出异常(当然上面的代码不会抛出异常),那么后面的任务都得不到执行。如果任务 C 抛出异常,那么任务 D 得不到执行。
那么我们怎么处理异常呢?看下面的代码,我们在任务 A 中抛出异常,并对其进行处理:
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
throw new RuntimeException();
}).exceptionally(ex -> "errorResultA")
.thenApply(resultA -> resultA + " resultB")
.thenApply(resultB -> resultB + " resultC")
.thenApply(resultC -> resultC + " resultD");
System.out.println(future.join());
上面的代码中,任务 A 抛出异常,然后通过 .exceptionally()
方法处理了异常,并返回新的结果,这个新的结果将传递给任务 B。所以最终的输出结果是:errorResultA resultB resultC resultD
参考
Java CompletableFuture 详解
CompletableFuture基本用法
使用了CompletableFuture之后,程序性能提升了三倍
编程老司机带你玩转 CompletableFuture 异步编程