CompletableFuturejava架构位置
CompletableFuture实现了Future接口和 CompletionStage接口 架构图如下
CompletionStage是什么
CompletionStage:代表异步计算过程中的某一个阶段,一个阶段完成以后可能会触发另外一个阶段
一句话总结:餐馆点菜
详情:我现在点的一个菜在餐馆中没有材料,老板同时要求3个人把所有材料全部都买回来了,然后厨师下锅把这些材料依次添加进去,最后出锅的是一道已经做好的菜品 而不是材料
一个阶段的计算或执行可以使Function,Consumer或者Runnable。
比如stage.thenApply( x -> square(x) ). thenAccept( x -> System.out.print(x) ). thenRun( () -> System.out.print() )
一个阶段的执行可能是被单个阶段的完成触发,也可能是有多个阶段一起触发
CompletableFuture是什么
CompletableFuture 提供了 Future的扩展功能,可以简化异步编程的复杂性,提供了函数式编程的能力,可以通过回调的方式处理计算结果,也提供了转换和组合 CompletableFuture 的方法
CompletableFuture可能代表一个明确的Future,也有可能代表一个完成阶段 ( CompletionStage ), 他支持在计算完成以后出发一些函数或执行某些动作
CompletableFuture实现了 Future接口就相当于是具有了FutureTask的特性,然后在这个基础上有添加了一个新的特性:CompletionStage接口
CompletableFuture的4个核心静态方法 创建一个异步操作
4个静态方法中,不用执行 future.get() 方法 程序会自动创建一个线程运行 sync内部的内容,如果调用了 future.get() 方法 使用的是 FutureTask的特性,会将线程阻塞
如果没有指定线程池,直接使用默认的ForkJoinPool.commonPool() 作为线程池执行异步代码
如果指定了线程池,就使用自定义的线程池执行异步代码
无返回值 runAsync()
public static CompletableFuture runAsync(Runnable runnable)
使用默认的线程池异步开启一个线程进行调用
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
System.out.println(Thread.currentThread().getName() + "异步操作");
});
System.out.println("main线程执行");
public static CompletableFuture runAsync(Runnable runnable, Executor executor)
使用自己创建的线程池开启一个线程进行调用
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
1,
5,
10,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
System.out.println(Thread.currentThread().getName() + "异步操作");
}, threadPoolExecutor);
System.out.println("main线程执行");
threadPoolExecutor.shutdown();
有返回值 supplyAsync()
public static CompletableFuture supplyAsync(Supplier supplier)
CompletableFuture<Integer> supplyAsync = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + "");
return 1;
});
System.out.println(supplyAsync.get());
System.out.println("main线程运行");
public static CompletableFuture supplyAsync(Supplier supplier, Executor executor)
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
1,
5,
10,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
CompletableFuture<Integer> supplyAsync = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + "");
return 2;
}, threadPoolExecutor);
System.out.println(supplyAsync.get());
System.out.println("main线程运行");
threadPoolExecutor.shutdown();

证明:CompletableFuture是一个和主线程无关的异步任务
注意:和主线程无关的前提是 CompletableFuture 没有调用 get,join,complete方法
功能:
-
创建一个一步线程,异步线程首先返回1024,然后进入 whenComplete方法,
-
supplyAsync中如果出错 那么打印出 e,v为null,
-
如果supplyAsync没有出错那么打印出v,e为null
-
如果以上步骤有出错的地方,那么直接进入 exceptionally,打印出异常信息并返回1
public class CompletableFutureTest {
public static void main(String[] args) {
//开启一个异步线程, 主线程执行完毕, 如果异步线程没有执行完也会被关闭
CompletableFuture<Integer> supplyAsync = CompletableFuture.supplyAsync(() -> {
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
return 1024;
}).whenComplete((v,e) ->{
System.out.println(v);
System.out.println(e);
}).exceptionally(e ->{
e.getMessage();
System.out.println("出现异常");
return 1;
});
System.out.println("main 线程执行......");
try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
}
}
证明和主线程无关:注意了 这里并没有调用 get() , join, complete 方法
如果主线程中 没有让主线程sleep 2s 结果如下
如果主线程中 让主线程sleep 2s 结果如下
如果调用了 get,或者 join方法,则会进入等待状态
案例:模拟一个比价功能
功能:现在有 淘宝,京东,拼夕夕,当当等多个网站售卖mysql 这本书,我现在要比较这本书在每个网站的价格是多少
package com.example.completablefuture;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
/**
* @create 2021-03-09 5:49 下午
*
* 比价案例
*/
public class CompletableFutureFunctionTest {
static List<NetMall> list = Arrays.asList(
new NetMall("jd"),
new NetMall("pdd"),
// new NetMall("tb"),
// new NetMall("tc"),
new NetMall("td")
);
/**
* 方式1,一个网站一个网站的得到商品的价格
* @param list
* @param productName
* @return
*/
public static List<String> getPriceByStep(List<NetMall> list, String productName){
// %.2f 表示保留2位小数
return list.stream()
.map(f -> String.format(productName + " in %s price is %.2f", f.getMallName(), f.calcPrice(f.getMallName())))
.collect(Collectors.toList());
}
/**
* 方式2, 使用CompletableFuture 并发处理
* @param list
* @param productName
* @return
*/
public static List<String> getPriceByStepSync(List<NetMall> list, String productName){
// 处理思路: 先将 List<NetMall> 转换成 -> List<CompletableFuture<String>>的list 然后对这个 list进行 join操作
return list.stream()
.map(f -> CompletableFuture.supplyAsync(() ->
String.format(productName + " in %s price is %.2f", f.getMallName(), f.calcPrice(f.getMallName()))))
.collect(Collectors.toList())
.stream()
.map(CompletableFuture::join)
.collect(Collectors.toList());
}
public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
long beforeSearchTime = System.currentTimeMillis();
List<String> lists = getPriceByStep(list, "mysql");
for (String element : lists) {
System.out.println(element);
}
long afterSearchTime = System.currentTimeMillis();
System.out.println(afterSearchTime - beforeSearchTime);
long beforeSearchTime1 = System.currentTimeMillis();
List<String> lists1 = getPriceByStepSync(list, "mysql");
for (String element : lists1) {
System.out.println(element);
}
long afterSearchTime1 = System.currentTimeMillis();
System.out.println(afterSearchTime1 - beforeSearchTime1);
}
}
@Getter
@AllArgsConstructor
class NetMall{
private String mallName;
public double calcPrice(String productName){
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
//ThreadLocalRandom.current().nextDouble() * 2 + productName.charAt(0); 表示随机生成一个浮点类型的数据
return ThreadLocalRandom.current().nextDouble() * 2 + productName.charAt(0);
}
}
结果如图所示:每添加一个新的网站,就会让一个网站一个网站的比较的那种方式变慢,而使用 CompletableFuture 的方式 几乎都是1s左右就执行完成

注意:使用CompletableFuture 并发处理 方法解读:
提问:有的人可能会觉得 第5、6这 2行 ( 红框框起来的2行 ) 可以删掉,没有什么实际作用,因为加了这2行是转换成 Stream<CompletableFuture>,不加这2行也是转换成 Stream<CompletableFuture>,那么为什么要加呢
解答:因为我需要操作的是 List<CompletableFuture> 而不是 Stream<CompletableFuture>,使用红框框起来的2句应该分开理解,如果不执行红框框起来的内容 虽然结果上是 Stream<CompletableFuture> 但实际上是缺了点东西的, 第二次调用 stream 的目的是 将了List<CompletableFuture>的list 转换成 stream,然后通过这个 stream执行 join操作
CompletableFuture中的常用方法
1. 获得结果和触发计算
1.1. get()、join()、get(long timeout, TimeUnit unit)
get()
:等待子线程完成任务,获得返回结果, 不管子任务要执行多久,都在外面等着 – FutureTask特性 一句话总结:
不见不散
join()
:和get() 方法一样,唯一的区别就是不抛出异常
public class GetResult {
public static void main(String[] args) {
CompletableFuture future = CompletableFuture.supplyAsync(() ->{
try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("进入异步方法");
return 1;
});
try { System.out.println(future.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); }
System.out.println(future.join());
}
}
get(long timeout, TimeUnit unit)
:等待 timeout 时间,如果该时间内子线程任务完成,则和get() 一样,如果该时间内子任务没有完成,则抛出 TimeOutException, 一句话总结:
过时不够
public class GetResult {
public static void main(String[] args) {
CompletableFuture future = CompletableFuture.supplyAsync(() ->{
try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("进入异步方法");
return 1;
});
try { System.out.println(future.get(1, TimeUnit.SECONDS)); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } catch (TimeoutException e) { e.printStackTrace(); }
}
}
1.2. getNow(T valueIfAbsent)
当执行到getNow时,如果程序执行完成,则取执行完成的那个结果,如果程序没有执行完成,则返回 getNow() 方法中传入的参数,
valueIfAbsent相当于是一个default
public class GetResult {
public static void main(String[] args) {
CompletableFuture future = CompletableFuture.supplyAsync(() ->{
try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("进入异步方法");
return 1;
});
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(future.getNow(2));
}
}
子线程已经计算完结果返回:
子线程没有计算完结果返回:
1.3. complete(T value)
complete(T value):当程序执行complete方法时,首先判断 completableFuture中的内容是否执行完了,如果执行完了,说明打断失败,返回completableFuture返回的内容,如果completableFuture中的内容没有执行完,那么就打断completableFuture中的内容,然后返回complete(T value)中的内容
complete(T value)常配合 get(), join()方法一起使用
public class GetResult {
public static void main(String[] args) {
CompletableFuture future = CompletableFuture.supplyAsync(() ->{
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("进入异步方法");
return 1;
});
try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
// System.out.println(future.getNow(2));
System.out.println(future.complete(2) + "," + future.join());
}
}
打断失败:
打断成功:
2. 对计算结果进行处理
2.1. thenApply方法
thenApply的计算结果存在依赖关系,线程串行化,由于依赖关系(当前不认错,不走下一步),当前步骤有异常的话就叫停
public class handleCalculation {
public static void main(String[] args) {
CompletableFuture future = CompletableFuture.supplyAsync(() -> {
try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("进入异步方法");
return 1;
}).thenApply(v1 -> {
System.out.println("----1");
// int i = 1 / 0;
return v1 + 1;
}).thenApply(v2 -> {
System.out.println("----2");
return v2 + 1;
}).whenComplete( (v, e) ->{
if(e == null){
System.out.println("---result" + v);
}
}).exceptionally(e ->{
e.printStackTrace();
return null;
});
System.out.println("future:" + future.join());
}
}
如果没有异常正常执行结果输出:
如果抛出异常:
thenApply后续部分就不走了,直接进入exceptionally中
2.2 handle
有异常也可以往下走,根据带的异常参数可以进一步处理
public class handleCalculation {
public static void main(String[] args) {
CompletableFuture future = CompletableFuture.supplyAsync(() -> {
try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("进入异步方法");
return 1;
}).handle((v1, e) -> {
System.out.println("----1");
int i = 1 / 0;
return v1 + 1;
}).handle((v2, e) -> {
System.out.println("----2");
return v2 + 1;
}).whenComplete( (v, e) ->{
if(e == null){
System.out.println("---result" + v);
}
}).exceptionally(e ->{
e.printStackTrace();
return null;
});
System.out.println("future:" + future.join());
}
}
如果出现异常 程序还是可以继续往后执行
2.3. whenComplete 、 exceptionally方法
当上一步操作完成了,判断上一步操作过程中是否出错,将上一步的操作结果、可能会出现的异常作为参数传入,
如果上一步正常执行,则第一个参数就是上一步执行的结果,异常信息就会显示为null
如果上一步异常执行,则第一个参数 为null, 异常信息 是这个异常信息, get() 的结果为 exceptionally() 方法中 return 的信息
举个例子:
//新开一个子线程干活, 有返回值
Integer integer = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + ":completableFuture2");
// int i = 10 / 0;
return 1024;
}).whenComplete((t, u) -> {
//如果正常执行,就会吧t打印出来
System.out.println(" --- t= " + t);
System.out.println(" --- u= " + u);
}).exceptionally(f -> {
//只有当程序出错的时候,才会进入这个方法,进入这个方法后, 也会让这个方法有一个异常信息
System.out.println("--exception:" + f.getMessage());
return 444;
}).get();
System.out.println(integer);
正常执行结果:
异常执行结果:
2.4. 总结thenApply()、handle()、whenComplete、exceptionally
exceptionally 相当于是try - catch
whenComplete + handle 相当于是 try - finally
2.5. 以上所有方法都存在一个带Async后缀的方法,2着的区别
拿thenApply举例:执行当前任务的线程继续执行 thenApply的任务:我最开始从线程池中拿了一个线程执行 supplyAsync方法后 依旧是拿这个线程执行thenApply方法
thenApplyAsync:执行吧 thenApplyAsync 这个任务继续交给线程池中其他线程来执行。我最开始从线程池中拿了一个线程执行 supplyAsync方法后,由线程池重新分配一个线程来执行 thenApplyAsync, 每次用一个 带Async后缀的方法,就会从线程池中拿一个
3.对结果进行消费
3.1 thenAccept
接收任务的处理结果,并消费处理,无返回结果
将上面的执行结果拿过来消费,没有返回内容
CompletableFuture.supplyAsync(() -> {
return 1 + 1;
}).thenApply(v -> {
return v + 1;
}).thenAccept(v -> {
System.out.println(v);
});
结果:
3.2 thenRun()
任务A执行完执行B,并且B不需要A的结果, 很少用到
public static void main(String[] args) {
System.out.println(CompletableFuture.supplyAsync(() -> {
return 1 + 1;
}).thenApply(v -> {
return v + 1;
}).thenRun(() -> {
}).join());
}
4.对计算速度进行选用
applyToEither:对计算结果进行优先选用
applyToEither( CompletionStage<? extends T> other, Function<? super T, U> fn)
public class ComsumeResult {
public static void main(String[] args) {
System.out.println(CompletableFuture.supplyAsync(() -> {
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
return 1;
}).applyToEither(CompletableFuture.supplyAsync(() -> {
try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
return 2;
}), r -> {
return r + 1;
}).join());
}
}
看哪个CompletableFuture 中的内容先执行完,就用先执行完的这个结果进行一系列操作 ,注意 虽然说优先选用执行速度快的那个作为参数,执行速度快的那个做了参数以后并不是说执行速度慢的那个就停了,执行速度慢的那个还是会执行完
5.对计算结果进行合并
先完成的先等着,等所有任务完成以后,再将结果合并
public static void main(String[] args) {
System.out.println(CompletableFuture.supplyAsync(() -> {
return 1;
}).thenCombine(CompletableFuture.supplyAsync(() -> {
return 2;
}), (r1, r2) -> {
return r1 + r2;
}).join());
}