java8-CompleableFuture的使用1
背景
- 硬件的极速发展,多核心CPU司空见惯;分布式的软件架构司空见惯;
- 功能API大多采用混聚的方式把基础服务的内容链接在一起,方便用户生活。
抛出了两个问题:
- 如何发挥多核能力;
- 切分大型任务,让每个子任务并行运行;
并发和并行的区别
项目 | 区别1 | 实现技术 |
---|---|---|
并行 | 每个任务跑在单独的cpu核心上 | 分支合并框架,并行流 |
并发 | 不同任务共享cpu核心,基于时间片调度 | CompletableFuture |
Future接口
java5开始引入。将来某个时刻发生的事情进行建模。
进行一个异步计算,返回一个执行运算的结果引用,当运算结束后,这个引用可以返回给调用方。
可以使用Future把哪些潜在耗时的任务放到异步线程中,让主线程继续执行其他有价值的工作,不在白白等待。
下面是一个例子:使用Future,可以让两个任务并发的运行,然后汇聚结果;
package com.test.completable;
import com.google.common.base.Stopwatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* 说明:Future应用实例
* @author carter
* 创建时间: 2019年11月18日 10:53
**/
public class FutureTest {
static final ExecutorService pool = Executors.newFixedThreadPool(2);
public static void main(String[] args) {
Stopwatch stopwatch = Stopwatch.createStarted();
Future<Long> longFuture = pool.submit(() -> doSomethingLongTime());
doSomething2();
try {
final Long longValue = longFuture.get(3, TimeUnit.SECONDS);
System.out.println(Thread.currentThread().getName() + " future return value :" + longValue + " : " + stopwatch.stop());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
pool.shutdown();
}
private static void doSomething2() {
Stopwatch stopwatch = Stopwatch.createStarted();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " doSomething2 :" + stopwatch.stop());
}
private static Long doSomethingLongTime() {
Stopwatch stopwatch = Stopwatch.createStarted();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " doSomethingLongTime : " + stopwatch.stop());
return 1000L;
}
}
没法编写简介的并发代码。描叙能力不够;比如如下场景:
- 将两个异步计算的结果合并为一个,这两个异步计算之间互相独立,但是第二个有依赖第一个结果。
- 等待Future中所有的任务都完成;
- 仅等待Future集合中最快结束的任务完成,并返回它的结果;
- 通过编程的方式完成一个Future任务的执行;
- 响应Future的完成事件。
基于这个缺陷,java8中引入了CompletableFuture 类;
实现异步API
技能点:
- 提供异步API;
- 修改同步的API为异步的API,如何使用流水线把两个任务合并为一个异步计算操作;
- 响应式的方式处理异步操作的完成事件;
类型 | 区别 | 是否堵塞 |
---|---|---|
同步API | 调用方在被调用运行的过程中等待,被调用方运行结束后返回,调用方取得返回值后继续运行 | 堵塞 |
异步API | 调用方和被调用方是异步的,调用方不用等待被调用方返回结果 | 非堵塞 |
package com.test.completable;
import com.google.common.base.Stopwatch;
import com.google.common.base.Ticker;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
/**
* 说明:异步调用计算价格的方法
* @author carter
* 创建时间: 2019年11月18日 13:32
**/
public class Test {
public static void main(String[] args) {
Shop shop = new Shop("BestShop");
Stopwatch stopwatch = Stopwatch.createStarted();
Stopwatch stopwatch2 = Stopwatch.createStarted();
Future<Double> doubleFuture = shop.getPriceFuture("pizza");
System.out.println("getPriceFuture return after: " + stopwatch.stop());
doSomethingElse();
try{
final Double price = doubleFuture.get();
System.out.println("price is " + price + " return after: " + stopwatch2.stop());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
private static void doSomethingElse() {
Stopwatch stopwatch = Stopwatch.createStarted();
DelayUtil.delay();
System.out.println("doSomethingElse " + stopwatch.stop());
}
}
错误处理
如果计算价格的方法产生了错误,提示错误的异常会被现在在试图计算商品价格的当前线程的范围内,最终计算的异步线程会被杀死,这会导致get方法返回结果的客户端永久的被等待。
如何避免异常被掩盖, completeExceptionally会把CompletableFuture内发生的问题抛出去。
private static void test2() {
Shop shop = new Shop("BestShop");
Stopwatch stopwatch = Stopwatch.createStarted();
Stopwatch stopwatch2 = Stopwatch.createStarted();
Future<Double> doubleFuture = shop.getPriceFutureException("pizza");
System.out.println("getPriceFuture return after: " + stopwatch.stop());
doSomethingElse();
try{
final Double price = doubleFuture.get();
System.out.println("price is " + price + " return after: " + stopwatch2.stop());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
方法改造:
//异步方式查询产品价格,异常抛出去
public Future<Double> getPriceFutureException(String product){
final CompletableFuture<Double> doubleCompletableFuture = new CompletableFuture<>();
new Thread(()->{try {
doubleCompletableFuture.complete(alculatePriceException(product));
}catch (Exception ex){
doubleCompletableFuture.completeExceptionally(ex);
}
}).start();
return doubleCompletableFuture;
}
无堵塞
即让多个线程去异步并行或者并发的执行任务,计算完之后汇聚结果;
private static void test3(String productName) {
Stopwatch stopwatch = Stopwatch.createStarted();
final List<String> stringList = Stream.of(new Shop("华强北"), new Shop("益田假日广场"), new Shop("香港九龙城"), new Shop("京东商城"))
.map(item -> String.format("商店:%s的商品:%s 售价是:%s", item.getName(), productName, item.getPrice(productName)))
.collect(Collectors.toList());
System.out.println(stringList);
System.out.println("test3 done in " + stopwatch.stop());
}
private static void test3_parallelStream(String productName) {
Stopwatch stopwatch = Stopwatch.createStarted();
final List<String> stringList = Stream.of(new Shop("华强北"), new Shop("益田假日广场"), new Shop("香港九龙城"), new Shop("京东商城"))
.parallel()
.map(item -> String.format("商店:%s的商品:%s 售价是:%s", item.getName(), productName, item.getPrice(productName)))
.collect(Collectors.toList());
System.out.println(stringList);
System.out.println("test3_parallelStream done in " + stopwatch.stop());
}
private static void test3_completableFuture(String productName) {
Stopwatch stopwatch = Stopwatch.createStarted();
final List<String> stringList = Stream.of(new Shop("华强北"), new Shop("益田假日广场"), new Shop("香港九龙城"), new Shop("京东商城"))
.map(item ->CompletableFuture.supplyAsync(()-> String.format("商店:%s的商品:%s 售价是:%s", item.getName(), productName, item.getPrice(productName))))
.collect(Collectors.toList())
.stream()
.map(CompletableFuture::join)
.collect(Collectors.toList());
System.out.println(stringList);
System.out.println("test3_completableFuture done in " + stopwatch.stop());
}
private static void test3_completableFuture_pool(String productName) {
Stopwatch stopwatch = Stopwatch.createStarted();
final List<String> stringList = Stream.of(new Shop("华强北"), new Shop("益田假日广场"), new Shop("香港九龙城"), new Shop("京东商城"))
.map(item ->CompletableFuture.supplyAsync(()-> String.format("商店:%s的商品:%s 售价是:%s", item.getName(), productName, item.getPrice(productName)),pool))
.collect(Collectors.toList())
.stream()
.map(CompletableFuture::join)
.collect(Collectors.toList());
System.out.println(stringList);
System.out.println("test3_completableFuture done in " + stopwatch.stop());
}
代码中有一个简单的计算场景,我想查询4家商店的iphone11售价;
华强北,益田苹果店,香港九龙城,京东商城;
每一家的查询大概耗时1s;
任务处理方式 | 耗时 | 优缺点说明 |
---|---|---|
顺序执行 | 4秒多 | 简单,好理解 |
并行流 | 1秒多 | 无法定制流内置的线程池,使用简单,改造简单 |
CompletableFuture 默认线程池 | 2秒多 | 默认线程池 |
CompletableFuture 指定线程池 | 1秒多 | 指定了线程池,可定制性更好,相比于并行流 |
多个异步任务的流水线操作
场景: 先计算价格,在拿到折扣,最后计算折扣价格;
private static void test4(String productName) {
Stopwatch stopwatch = Stopwatch.createStarted();
final List<String> stringList = Stream.of(new Shop("华强北"), new Shop("益田假日广场"), new Shop("香港九龙城"), new Shop("京东商城"))
.map(shop->shop.getPrice_discount(productName))
.map(Quote::parse)
.map(DisCount::applyDiscount)
.collect(Collectors.toList());
System.out.println(stringList);
System.out.println("test4 done in " + stopwatch.stop());
}
private static void test4_completableFuture(String productName) {
Stopwatch stopwatch = Stopwatch.createStarted();
final List<String> stringList = Stream.of(new Shop("华强北"), new Shop("益田假日广场"), new Shop("香港九龙城"), new Shop("京东商城"))
.map(shop->CompletableFuture.supplyAsync(()->shop.getPrice_discount(productName),pool))
.map(future->future.thenApply( Quote::parse))
.map(future->future.thenCompose(quote -> CompletableFuture.supplyAsync(()->DisCount.applyDiscount(quote),pool)))
.collect(Collectors.toList())
.stream()
.map(CompletableFuture::join)
.collect(Collectors.toList());
System.out.println(stringList);
System.out.println("test4_completableFuture done in " + stopwatch.stop());
}
以上是有依赖关系的两个任务的聚合,即任务2,依赖任务1的结果。使用的是thenCompose方法;
接下来如果有两个任务可以异步执行,最后需要依赖着两个任务的结果计算得到最终结果,采用的是thenCombine;
//两个不同的任务,最后需要汇聚结果,采用combine
private static void test5(String productName) {
Stopwatch stopwatch = Stopwatch.createStarted();
Shop shop = new Shop("香港九龙");
Double pricefinal = CompletableFuture.supplyAsync(()->shop.getPrice(productName))
.thenCombine(CompletableFuture.supplyAsync(shop::getRate),(price, rate)->price * rate).join();
System.out.println("test4 done in " + stopwatch.stop());
}
completion事件
让任务尽快结束,无需等待;
有多个服务来源,你请求多个,谁先返回,就先响应;
结果依次返回:
//等待所有的任务执行完毕; CompletableFuture.allOf()
public void findPriceStream(String productName){
List<Shop> shops = Arrays.asList(new Shop("华强北"), new Shop("益田假日广场"), new Shop("香港九龙城"), new Shop("京东商城"));
final CompletableFuture[] completableFutureArray = shops.stream()
.map(shop -> CompletableFuture.supplyAsync(() -> shop.getPrice_discount(productName), pool))
.map(future -> future.thenApply(Quote::parse))
.map(future -> future.thenCompose(quote -> CompletableFuture.supplyAsync(() -> DisCount.applyDiscount(quote), pool)))
.map(f -> f.thenAccept(System.out::println))
.toArray(size -> new CompletableFuture[size]);
CompletableFuture.allOf(completableFutureArray).join();
}
多个来源获取最快的结果:
//有两个获取天气的途径,哪个快最后结果就取哪一个
public static void getWeather(){
final Object join = CompletableFuture.anyOf(CompletableFuture.supplyAsync(() -> a_weather()), CompletableFuture.supplyAsync(() -> b_weather())).join();
System.out.println(join);
}
private static String b_weather() {
DelayUtil.delay(3);
return "bWeather";
}
private static String a_weather() {
DelayUtil.delay(5);
return "aWeather";
}
源码分析
可完备化的将来;CompletableFuture ;
先看签名:
public class CompletableFuture<T> implements Future<T>, CompletionStage<T> {}
实现了Futrue,CompletionStage接口;
这两个接口简单说明一下:
接口 | 关键特性 |
---|---|
Future | 直接翻译为未来,标识把一个任务异步执行,需要的的时候,通过get方法获取,也可以取消cancel,此外还提供了状态查询方法,isDone, isCancled,实现类是FutureTask |
CompletionStage | 直接翻译是完成的阶段,提供了函数式编程方法 |
可以分为如下几类方法
方法 | 说明 |
---|---|
thenApply(Function f) | 当前阶段正常完成之后,返回一个新的阶段,新的阶段把当前阶段的结果作为参数输入; |
thenConsume(Consumer c), | 当前阶段完成之后,结果作为参数输入,直接消费掉,得到不返回结果的完成阶段; |
thenRun(Runnable action), | 不接受参数,只是继续执行任务,得到一个新的完成阶段; |
thenCombine(otherCompletionStage,BiFunction), | 当两个完成阶段都完成的时候,执行BIFunction,返回一个新的阶段; |
thenAcceptBoth(OtherCompletionStage, BiConsumer) | 两个完成阶段都完成之后,对两个结果进行消费; |
runAfterBoth(OtherCompletionStage,Runable) | 两个完成阶段都完成之后,执行一个动作; |
applyToEither(OtherCompletionStage,Function) | 两个完成阶段的任何一个执行结束,进入函数操作,并返回一个新的阶段 |
acceptEither(OtherCompletionStage,Consumer) | 两个完成阶段的任何一个执行结束,消费掉,返回一个空返回值的完成阶段 |
runAfterEither(OtherCompletionStage,Runable) | 两个完成阶段的任何一个结束,执行一个动作,返回一个空返回值的完成阶段 |
thenCompose(Function) | 当前阶段完成,返回值作为参数,进行函数运算,然后结果作为一个新的完成阶段 |
exceptionally(Function) | 无论当前阶段是否正常完成,消费掉异常,然后返回值作为一个新的完成阶段 |
whenComplete | |
handle | 无论当前完成阶段是否正常结束,都执行一个BIFunction的函数,并返回一个新结果作为一个新的完成阶段 |
toCompletableFuture | 转换为ComplatableFuture |
里面的实现细节后面单独成文章再讲。
小结
- 执行一些比较耗时的操作,尤其是依赖一个或者多个远程服务的操作,可以使用异步任务改善程序的性能,加快程序的响应速度;
- 使用CompletableFuture你可以轻松的实现异步API;
- CompletableFuture提供了异常管理机制,让主线程有机会接管子任务抛出的异常;
- 把同步API封装到CompletableFuture中,可以异步得到它的结果;
- 如果异步任务之间互相独立,而他们之间的某一些结果是另外一些的输入,可以把这些任务进行compose;
- 可以为CompletableFuture中的任务注册一个回调函数,当任务执行完毕之后再进行一些其它操作;
- 你可以决定什么时候结束程序的运行,是所有的CompletableFuture任务所有对象执行完毕,或者只要其中任何一个完成即可。
原创不易,转载请注明出处。
java8-CompleableFuture的使用1的更多相关文章
- Java8实战分享
虽然很多人已经使用了JDK8,看到不少代码,貌似大家对于Java语言or SDK的使用看起来还是停留在7甚至6. Java8在流式 or 链式处理,并发 or 并行方面增强了很多,函数式的风格使代码可 ...
- java8中lambda表达式的应用,以及一些泛型相关
语法部分就不写了,我们直接抛出一个实际问题,看看java8的这些新特性究竟能给我们带来哪些便利 顺带用到一些泛型编程,一切都是为了简化代码 场景: 一个数据类,用于记录职工信息 public clas ...
- Android Studio2.1.2 Java8环境下引用Java Library编译出错
转载请注明出处:http://www.cnblogs.com/LT5505/p/5685242.html 问题:在Android Studio2.1.2+Java8的环境下,引用Java Librar ...
- Java笔记——Java8特性之Lambda、方法引用和Streams
Java8已经推出了好一段时间了,而掌握Java8的新特性也是必要的,如果要进行Spring开发,那么可以发现Spring的官网已经全部使用Java8来编写示例代码了,所以,不学就看不懂. 这里涉及三 ...
- 关于Java8函数式编程你需要了解的几点
函数式编程与面向对象的设计方法在思路和手段上都各有千秋,在这里,我将简要介绍一下函数式编程与面向对象相比的一些特点和差异. 函数作为一等公民 在理解函数作为一等公民这句话时,让我们先来看一下一种非常常 ...
- Java8并发教程:Threads和Executors
来之:ImportNew 欢迎阅读我的Java8并发教程的第一部分.这份指南将会以简单易懂的代码示例来教给你如何在Java8中进行并发编程.这是一系列教程中的第一部分.在接下来的15分钟,你将会学会如 ...
- java8 学习系列--NIO学习笔记
近期有点时间,决定学习下java8相关的内容: 当然了不止java8中新增的功能点,整个JDK都需要自己研究的,不过这是个漫长的过程吧,以自己的惰性来看: 不过开发中不是有时候讲究模块化开发么,那么我 ...
- Java8函数式编程
在Java8的 java.util.function中包含以下几个接口 1.Function,先上源码 /* * Copyright (c) 2010, 2013, Oracle and/or its ...
- Java8闭包
闭包在很多语言中都存在,例如C++,C#.闭包允许我们创建函数指针,并把它们作为参数传递,Java编程语言提供了接口的概念,接口中可以定义抽象方法,接口定义了API,并希望用户或者供应商来实现这些方法 ...
- Java8新特性——接口的默认方法和类方法
Java8新增了接口的默认方法和类方法: 以前,接口里的方法要求全部是抽象方法,java8以后允许在接口里定义默认方法和类方法: 不同的是: 默认方法可以通过实现接口的类实例化的对象来调用,而类方法只 ...
随机推荐
- redis(7)--redis应用实战
问题1:哨兵模式下客户端应该连接哪个redis-server? 问题2:集群模式下为什么会有MOVED error Redis Java客户端介绍 已有的客户端支持 Redis Java客户端有很多的 ...
- Electron 设置 -webkit-app-region 后无法响应鼠标点击事件的解决方式
参考博客:https://blog.csdn.net/qq_20264891/article/details/87721163
- 【原创】005 | 搭上SpringBoot请求处理源码分析专车
前言 如果这是你第二次看到师长,说明你在觊觎我的美色! 点赞+关注再看,养成习惯 没别的意思,就是需要你的窥屏^_^ 专车介绍 该趟专车是开往Spring Boot请求处理源码分析专车,主要用来分析S ...
- 【玩转SpringBoot】给自动配置来个整体大揭秘
上一篇文章中提到的条件注解,只是自动配置整体解决方案中的一个环节而已,可以说是管中窥豹. 本文就逐步擦除迷雾,让整体浮现出来,这样就会有一个宏观的认识. 除了写代码之外,还能干点什么? 提到“配置”这 ...
- 数组(Array)和列表(ArrayList)有什么区别?什么时候应该使用Array而不是ArrayList?
下面列出了Array和ArrayList的不同点:Array可以包含基本类型和对象类型,ArrayList只能包含对象类型.Array大小是固定的,ArrayList的大小是动态变化的.ArrayLi ...
- android studio 刚安装需要配置的东西
智能提示 调整log区域的字体 快捷键中文乱码 自动导入包 意思是创建成员变量的时候,以m开头 下载插件 提高编译的速度
- mysql取消严格模式
配置文件my.ini sql-mode="STRICT_TRANS_TABLES,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION" 修改为 s ...
- 【ES6基础】let、const命令和变量的结构赋值
ES5声明变量(2):var .function ES6声明变量(6):var.function.let.const.import和class 1.let命令和const命令 (1)let和const ...
- pt-online-schema-change工具使用教程(在线修改大表结构)
percona-toolkit中pt-online-schema-change工具安装和使用 pt-online-schema-change介绍 使用场景:在线修改大表结构 在线数据库的维护中,总会涉 ...
- Tomcat系列(二)- EndPoint源码解析
在上一节中我们描述了Tomcat的整体架构, 我们知道了Tomcat分为两个大组件,一个连接器和一个容器. 而我们这次要讲的 EndPoint的组件就是属于连接器里面的. 它是一个通信的端点,就是负责 ...