写在前面:各位看到此博客的小伙伴,如有不对的地方请及时通过私信我或者评论此博客的方式指出,以免误人子弟。多谢!
先列一下CompletableFuture 中4个异步执行任务静态方法:
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) {
return asyncSupplyStage(asyncPool, supplier);
}
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier,Executor executor) {
return asyncSupplyStage(screenExecutor(executor), supplier);
}
public static CompletableFuture<Void> runAsync(Runnable runnable) {
return asyncRunStage(asyncPool, runnable);
}
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor) {
return asyncRunStage(screenExecutor(executor), runnable);
}
其中supplyAsync用于有返回值的任务,runAsync则用于没有返回值的任务。Executor参数可以手动指定线程池,否则默认ForkJoinPool.commonPool()系统级公共线程池。
注意:这些线程都是Daemon线程,主线程结束Daemon线程不结束,只有JVM关闭时,生命周期终止。
案例演示:
示例一
future.get()在等待执行结果时,程序会一直阻塞,直到结果返回,也可以调用complete() 方法手动的结束一个Future,调用complete(T t)会立即执行。但是complete(T t)只能调用一次,后续的重复调用会失效。如果future已经执行完毕能够返回结果,此时再调用complete(T t)则会无效。
public static void main(String[] args) throws Exception{
complete();
}
static void complete() throws Exception{
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + ": 开始执行");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ": 执行结束");
return "Hello";
});
future.complete("World");
try {
System.out.println(Thread.currentThread().getName() + " :" + future.get());
} catch (Exception e) {
e.printStackTrace();
}
}
执行结果如下:
从执行结果来看,只有主线程执行了,另一个异步线程没有执行,可以看出complete(T value)是立即生效的,并且返回了complete的值value。
改造一下上面的方法,在调用complete前先让主线程sleep三秒,
public static void main(String[] args) throws Exception{
complete();
}
static void complete() throws Exception{
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + ": 开始执行");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ": 执行结束");
return "Hello";
});
Thread.sleep(3000);
future.complete("World");
try {
System.out.println(Thread.currentThread().getName() + " :" + future.get());
} catch (Exception e) {
e.printStackTrace();
}
}
执行结果如下:
从执行结果来看,ForkJoinPool线程池的一个线程执行完之后,主线程才打印,并且打印的是Hello,说明如果future已经执行完毕能够返回结果,此时再调用complete(T t)则会无效。
示例二:串行关系
使用thenApplyAsync实现当前任务正常完成以后执行,当前任务的执行结果可以作为下一任务的输入参数。
static void thenAccept(){
CompletableFuture<String> futureA = CompletableFuture.supplyAsync(() -> "任务A");
CompletableFuture<String> futureB = futureA.thenApplyAsync(a -> {
System.out.println("执行任务B.");
System.out.println("参数:" + a);
return "a";
});
try {
System.out.println(futureB.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
执行结果如下:
先回顾一下上一篇介绍的then吧,then直译【然后】,也就是表示下一步,可以串行化任务的执行,而apply为function接口方法,有参数,有返回值,所以很容易就从thenApplyAsync方法名看出,这是一个可以使任务串行化执行并且有输入参数和返回值的方法,是不是感觉上一篇介绍的也很有用呢?
上面示例代码中,使用CompletableFuture的supplyAsync方法创建一个有返回值的CompletableFuture(不需要返回值可以使用runAsync()的两个构造方法),调用futureA的thenApplyAsync方法后,就使futureA和futureB串行化执行,并把futureA执行完后的结果当做futureB的参数,最后通过futureB的get()方法获取到futureB的结果。可串行化执行多个任务,如:
static void thenApply1(){
CompletableFuture<String> futureA = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> futureB = futureA.thenApply(s -> s + "World");
CompletableFuture<String> futureC = futureB.thenApply(String::toLowerCase);
System.out.println(futureC.join());
}
串行三个任务,输出结果为helloworld。
相对的,如果不关心上一步的结果,直接执行下一个操作可以使用thenRun()方法。
示例三:聚合(两个/多个)And关系
我们知道combine 和 both 都是要求两者都满足,也就是 and 的关系。聚合关系一般适用于,要得到一个结果,需要依赖前面多个查询的结果的场景,如想要获取商品的当前价,需要先查询原始价格,再查询折扣,才能得到现价,下面使用thenCombine聚合两个独立 Future 的结果,如果不需要返回结果,可以使用thenAcceptBoth(..)。
static void thenCombine() throws Exception{
Long startTime = System.currentTimeMillis();
CompletableFuture<Integer> futureA = CompletableFuture.supplyAsync(() -> {
Integers = null;
try {
s = task1();
} catch (Exception e) {
e.printStackTrace();
}
return s;
});
CompletableFuture<Integer> futureB = CompletableFuture.supplyAsync(() -> {
Integers = null;
try {
s = task2();
} catch (Exception e) {
e.printStackTrace();
}
return s;
});
CompletableFuture result = futureA.thenCombine(futureB,(a,b) ->{
return a + b;
});
System.out.println(result.get());
Long endTime = System.currentTimeMillis();
System.out.println("耗时:" + (endTime - startTime));
futureA.thenAcceptBoth(futureB,(a,b) ->{
System.out.println(a + b);
});
}
static Integertask1() throws Exception{
System.out.println("任务1开始执行");
Thread.sleep(2000);
System.out.println("任务1结束执行");
return 1;
}
static Integertask2() throws Exception{
System.out.println("任务2开始执行");
Thread.sleep(2000);
System.out.println("任务2结束执行");
return 2;
}
如上通过thenCombine()方法将将两个future聚合起来,通过thenCombine的源码看出他有两个参数,第一个参数为需要聚合的另一个CompletableFuture,第二个参数为BiFunction,BiFunction可以接收连个参数并且有返回值,
如上演示代码调用CompletableFuture的thenCombine()方法,传入futureB,和两个future的返回结果,计算结果为3
执行结果如下:
从结果来看,thenCombine实现了连个CompletableFuture的聚合,并计算出了他们的结果之和,并且从时间上来看只用了2047毫秒,效率提示了一倍,平时这种场景还是比较多的,但是遗憾的是thenCombine只能聚合两个CompletableFuture的结果。如果想要聚合多个结果,CompletableFuture也是可以实现的,可以使用 allOf()方法,
由源码看出,聚合多少个都可以,但是没有返回值,下面模拟一个场景,查询一个商品详情,需要分别去查商品信息,卖家信息,库存信息,订单信息等,这些查询相互独立,在不同的服务上, 假设每个查询都需要一到两秒钟,要求总体查询时间小于2秒,看如下代码:
static void allOf(){
ExecutorService executorService = Executors.newCachedThreadPool();
Long startTime = System.currentTimeMillis();
CompletableFuture<String> futureA = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(1000 + new Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
return "商品详情";
},executorService);
CompletableFuture<String> futureB = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(1000 + new Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
return "卖家信息";
},executorService);
CompletableFuture<String> futureC = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(1000 + new Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
return "库存信息";
},executorService);
CompletableFuture<String> futureD = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(1000 + new Random().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());
Long endTime = System.currentTimeMillis();
System.out.println("耗时:" + (endTime - startTime));
}
执行结果如下:
可以看出,使用allOf聚合了四个CompletableFuture并且也达到了预期的效率。
提到allOf:当所有的CompletableFuture都执行完后执行计算,还有一个anyOf,它表示最快的那个CompletableFuture执行完之后执行计算,就不再贴代码了。
示例四:聚合(两个/多个)Or关系
我们知道Either 表示两者中的一个,自然也就是 Or 的体现了,如示例三中,先介绍一下可以聚合处理两个CompletableFuture的方法,如applyToEither():执行两个CompletionStage的结果,哪个先执行完了,就是用哪个的返回值进行下一步操作。
演示代码如下:
static void applyToEither(){
CompletableFuture<String> futureA = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "通过方式A获取结果";
});
CompletableFuture<String> futureB = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "通过方式B获取结果";
});
CompletableFuture<String> futureC = futureA.applyToEither(futureB,
result -> "结果:" + result);
System.out.println(futureC.join());
}
无论执行多少次返回结果都是B,PS:join()、get()两个方法都是获取结果。类似于thenCombine 不需要返回结果时可使用acceptEither(),如果从多个future选取最快的一个可以使用anyOf()方法。