CompletableFuture
Future接口理论知识复习
- Future接口(FutureTask实现类)定义了操作异步任务执行一些方法,如获取异步任务的执行结果,取消任务,判断任务是否被取消,判断任务是否完毕等
- 比如主线程让一个子线程取执行任务,子线程可能比较耗时,启动子线程开始执行任务后,主线程去做其他事情了,过了一会才去获取子任务的执行结果或变更的任务状态
- 一句话:Future接口可以为主线程开一个分支任务,专门为主线程处理耗时和费力地复杂业务
Future接口常用的实现类FutureTask异步任务
Future接口能干什么
- Future是Java5新加的一个接口,提供了一种一部并行计算的功能,如果主线程需要执行一个很耗时的计算任务,我们就可以通过future把这个任务放到异步线程中执行,主线程继续处理其他任务或者先行结束,再通过Future获取计算结果
- Runnable接口
- Callable接口
- Runnable重新run方法,Callable重写call方法;Runnable无返回值,Callable有返回值;Runnable不能抛异常,Callable能抛异常
- callable和runnable都可以应用于executors。而thread类只支持runnable及其子类.
public static void main(String[] args) throws Exception{
//1 创建一个线程池
//调用Executors类的静态方法
ExecutorService service = Executors.newFixedThreadPool(10);
//2提交runnable对象
service.submit(new Runnable() {
@Override
public void run() {
}
});
//3 提交callable对象
service.submit(new Callable<String>() {
@Override
public String call() throws Exception {
return null;
}
});
//4 关闭线程池
service.shutdown();
}
- Future接口和FutureTask实现类
目的:异步多线程任务执行且返回有结果,三个特点:多线程/有返回/异步任务
thread支持runnable(多线程),runable的实现接口runableFuture以及子类FutureTask支持异步任务,但是FutureTask支持构造注入Callable;符合上面的三个条件
本源的Future接口相关架构
Future编码实战和优缺点分析
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<String> futureTask = new FutureTask<>( () -> {
return "hello callable";
});
Thread thread = new Thread(futureTask);
thread.start();
System.out.println(futureTask.get());
}
优点
- future+线程池异步多线程任务配置,能显著提高程序的执行效率
缺点
- get()阻塞,如果get()位置不正确会阻塞程序的运行 解决办法:get(3,TimeUnit.SECONDS):3秒钟后如果未执行完毕,抛出异常
- isDone轮询会导致CPU空转:轮询的方式会耗费cpu资源,而且也不见得能及时地得到计算结果.如果想要异步获取结果,通常都会以轮询的方式去获取结果,尽量不要阻塞;
while (true){
if (futureTask.isDone()) {
System.out.println(futureTask.get());
break;
}else {
TimeUnit.SECONDS.sleep(1);
}
}
结论:Future对于结果的获取不是很友好,只能通过阻塞或者轮询的方式得到任务的结果
想完成一些复杂的任务
对于简单的业务场景使用Future完全ok
回调通知
- 应对Future的完成时间,完成了可以告诉我,也就是我们的回调通知,通过轮询的方式去判断任务是否完成这样非常占CPU并且代码也不优雅
创建异步任务
- Future+线程池配合
多个任务前后依赖可以组合处理
- 想将多个异步任务的计算结果组合起来,后一个异步任务的计算结果需要前一个异步任务的值,将两个或者多个异步任务合成一个异步计算,这几个异步计算互相独立,同时后面这个又依赖前一个处理的
对计算速度选最快
- 当Future集合中某个任务最快结束时,返回结果,返回第一名处理结果
总结:使用Future之前提供的那点API处理起来不够优雅,CompletableFuture以声明式的方式优雅的处理这些需求,Future能干的,CompletableFuture都能干.
ComplatableFuture对Future的改进
ComplatableFuture为什么出现
- get()方法在Future计算完成之前会一直处于阻塞状态
- isDone()方法容易耗费CPU资源
- 对于真正的异步任务我们希望是可以通过传入回调函数,在Future结束时自动调用该回调函数,这样,我们就不用等待结果.
- 阻塞的方式和异步编程的设计理念相违背,而轮询的方式会耗费无谓的CPU资源.因此设计出ComplatableFuture
- ComplatableFuture提供了一种观察者模式类似的机制,可以让任务执行完成后通知监听的一方
ComplatableFuture和CompletionStage源码分别介绍
接口CompletionStage
- CompletionStage代表异步计算过程中的某一个阶段,一个阶段完成以后可能会触发另外一个阶段
- 一个阶段的计算执行可以是一个Function,Consumer或者Runnable.比如:stage.thenApply(x ->square(x)).thenAccept(x -> sout(x)).thenRun(()->sout)
- 一个阶段的执行可能是单个阶段的完成触发,也可能是多个阶段一起触发
- 代表异步计算过程中的某一个阶段,一个阶段完成可能会触发另外一个阶段,
接口ComplatableFuture
- 在Java8中,CompletableFuture提供了非常强大的Future的扩展功能,可以帮助我们简化异步编程的复杂性,并且提供了函数式编程的能力,可以通过回调的方式处理计算结果,也提供了转换和组合ComplatableFuture的方式
- 他可能代表一个明确完成的Future,也有可能代表一个完成阶段(CompletionStage),它支持在完成计算后触发一些函数式或执行某些动作
- 它实现了Future和CompletionStage接口
核心的四个静态方法,来创建一个异步任务
runAsync无返回值
//返回一个新的CompletableFuture,在{@link ForkJoinPool#commonPool()}中运行的任务运行给定操作后,该任务将异步完成。
//@param runnable the action to run before completing the returned CompletableFuture
//@return the new CompletableFuture
public static CompletableFuture<Void> runAsync(Runnable runnable)
//返回一个新的CompletableFuture,该Future由运行给定操作后在给定执行器中运行的任务异步完成。
//@param runnable在完成返回的CompletableFuture之前运行操作
//@param executor用于异步执行的执行器
//@返回新的CompletableFuture
public static CompletableFuture<Void> runAsync(Runnable runnable,Executor executor)
supplyAsync有返回值
最常用的方法
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier,Executor executor)
上述Executor executor参数说明
- 没有指定Executor的方法,直接使用默认的ForkJoinPool.commonPool()作为他的线程池执行异步代码
- 如果执行线程池,则使用我们自定义的或者特别的线程池执行异步代码
Code
Code之通用演示,减少阻塞和轮询
- 从java8开始引入了CompletionFuture,他是Future的功能增强版,减少阻塞和轮询,可以传入回调对象,当异步任务完成或者发生异常时,自动调用回调对象的回调方法
- 主线程不要立刻结束,否则CompletionFuture默认使用的线程池会立刻关闭;可以自定义线程池,但是要记得关闭
CompletionFuture的优点
- 异步任务结束时,会自动调用某个对象的方法;
- 主线程设置好会掉之后,不再关心异步任务的执行,异步任务之间可以顺序执行
- 异步任务出错时,会自动回调某个对象的方法
案例精讲
函数式编程成为主流
- Java8函数式编程用过吗
- Lambda表达式+Stream流式调用+Chain链式调用+Java8函数式编程
- Chain链式调用:需要在实体类上面添加@Accessors(chain = true)此注解属于lombok的注解,需要引入相关jar包
先说说join和get对比
- 都可以从CompletableFuture中获取到返回值
- 区别:编译时是否报错
需求说明
- 同一款产品,同时搜索出同款产品在各大电商平台的售价
- 同一款产品,同时搜索出本产品在同一个电商平台下,各个入驻卖家售价是多少
- 输出返回:希望是同款产品在不同地方的价格清单列表,返回一个List
- <> in jd price is 88.05
- <> in dangdang price is 88.05
- <> in taobao price is 88.05
- 解决方案:比对同一个产品在各大平台的价格,要求获得一个清单列表
- 方案1:查完京东查淘宝…
- 方案2:万箭齐发,一口气多线程异步任务同时查询
public class CompletableFutureMall {
static List<NetMall> list = Arrays.asList(
new NetMall("jd"),
new NetMall("dangdang"),
new NetMall("taobao")
);
public static List<String> getPriceByCompletableFuture(List<NetMall> list, String productName){
return list.stream().map(netMall -> CompletableFuture.supplyAsync(() -> String.format(productName + " in %s price is %.2f",
netMall.getNetMallNmame(),
netMall.calPrice(productName))))
.collect(Collectors.toList())
.stream()
.map(s -> s.join())
.collect(Collectors.toList());
}
public static void main(String[] args) {
long start = System.currentTimeMillis();
getPriceByCompletableFuture(list, "mysql");
long end = System.currentTimeMillis();
System.out.println("============>" + (end - start));
}
}
class NetMall{
@Getter
private String netMallNmame;
public NetMall(String netMallNmame){
this.netMallNmame = netMallNmame;
}
public double calPrice(String productName){
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
return ThreadLocalRandom.current().nextDouble() * 2 + productName.charAt(0);
}
}
CompeletableFuture常用方法
获得结果和触发计算
- 获取结果
- get()
- get(long timeout ,TimeUnit unit)
- join()
- getNow(T valueIfAbsent):如果计算完成返回计算的结果值或者抛出的异常,如果没有计算完成,则返回给的默认值
- 没有计算完成的情况下,给我一个替代结果
- 计算完,返回计算完成后的结果(或者是异常)
- 没计算完,完成设定的valueIfAbsent值
- 主动触发计算
- public boolean complete(T value):是否打断get方法立即返回括号值
对计算结果进行处理
thenApply
- 计算结果存在依赖关系,这两个线程串行化
- code
CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("====> 111" );
return 1;
}).thenApply( item1 -> {
int i = 10/0;
System.out.println("====> 222");
return item1 + "12";
}).thenApply( item2 -> {
System.out.println("====> 333");
return Integer.valueOf(item2);
}).whenComplete( (value, exception) -> {
//这一步要对exception做判断
if (exception != null){
System.out.println("=======>whenComplete");
System.out.println(exception);
}else {
//如果不对exception做判断可能会影响到值得使用,因为获取到的值为null;
System.out.println("计算结果为:==>" + value);
}
}).exceptionally( e -> {
System.out.println("=======>exceptionally");
System.out.println(e);
return null;
});
//打印结果:执行完第一个,就直接执行whenComplete和exceptionally
====> 111
=======>whenComplete
java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
=======>exceptionally
java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
============>1015
- 异常相关:由于存在依赖关系(当前错,不走下一步),当前步骤有异常的化就直接叫停
handle
- 计算结果存在依赖关系,这两个线程串行化
- code
CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("====> 111" );
return 1;
}).handle( (item1, e) -> {
int i = 10/0;
System.out.println("====> 222");
return item1 + "12";
}).handle( (item2, e) -> {
System.out.println("====> 333");
return Integer.valueOf(item2);
}).whenComplete( (value, exception) -> {
//这一步要对exception做判断
if (exception != null){
System.out.println("=======>whenComplete");
System.out.println(exception);
}else {
//如果不对exception做判断可能会影响到值得使用,因为获取到的值为null;
System.out.println("计算结果为:==>" + value);
}
}).exceptionally( e -> {
System.out.println("=======>exceptionally");
System.out.println(e);
return null;
});
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
//打印结果:执行完第一个虽然第二个报错,但是仍然执行完了第三个,然后执行whenComplete和exceptionally
====> 111
====> 333
=======>whenComplete
java.util.concurrent.CompletionException: java.lang.NumberFormatException: null
=======>exceptionally
java.util.concurrent.CompletionException: java.lang.NumberFormatException: null
============>1015
- 异常相关:有异常也可以往下一步走,根据带的参数可以进一步处理
总结
- exceptionally相当于try/catch
- whenComplete+handle相当于try/finally
对计算结果进行消费
接收人任务的处理结果,并消费处理,无返回结果
- 消费性接口
thenAccept
CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("====> 111" );
return 1;
}).thenApply( item1 -> {
int i = 10/0;
System.out.println("====> 222");
return item1 + "12";
}).thenApply( item2 -> {
System.out.println("====> 333");
return Integer.valueOf(item2);
}).thenAccept((integer -> {
System.out.println(integer);
//如果加了whenComplete注意thenAccept是一个消费性接口.并不会有返回值,所以value一定为null
})).whenComplete( (value, exception) -> {
//这一步要对exception做判断
if (exception != null){
System.out.println("=======>whenComplete");
System.out.println(exception);
}else {
System.out.println("计算结果为:==>" + value);
}
//如果有异常,就会走下面这个方法
}).exceptionally( e -> {
System.out.println("=======>exceptionally");
System.out.println(e);
return null;
});
对比补充
- Code之任务之间的顺序执行
- thenRun
- 任务A执行完执行B,并且B不需要A得结果
- thenAccept
- 任务A执行完执行B,B需要A得结果,但是任务B无返回值
- thenApply
- 任务A执行完执行B,B需要A得结果,同时任务B有返回值
- Code
- thenRun
CompletableFuture和线程池说明
- thenRun和thenRunAsync为例,有什么区别??
- 没有传入自定义线程池,都用默认的线程池ForkJoinPool
- 传入了一个自定义线程池,如果你执行完第一个任务的时候,传入了一个自定义线程池
- 调用thenRun方法执行第二个任务时,则第二个任务和第一个任务公用一个线程池
- 调用thenRunAsync方法执行第二个任务时,第一个任务公用自定义得线程池,第二个任务使用的是ForkJoin线程池
对计算速度进行选用
applyToEither(谁快用谁)
对计算结果进行合并
- 两个CompletionStage任务都完成后,最终把两个任务的结果一起交给thenCompine来处理,先完成的先等着,等待其他分支任务