背景

  1. 硬件的极速发展,多核心CPU司空见惯;分布式的软件架构司空见惯;
  2. 功能API大多采用混聚的方式把基础服务的内容链接在一起,方便用户生活。

抛出了两个问题:

  1. 如何发挥多核能力;
  2. 切分大型任务,让每个子任务并行运行;

并发和并行的区别

项目 区别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;
} }

没法编写简介的并发代码。描叙能力不够;比如如下场景:

  1. 将两个异步计算的结果合并为一个,这两个异步计算之间互相独立,但是第二个有依赖第一个结果。
  2. 等待Future中所有的任务都完成;
  3. 仅等待Future集合中最快结束的任务完成,并返回它的结果;
  4. 通过编程的方式完成一个Future任务的执行;
  5. 响应Future的完成事件。

基于这个缺陷,java8中引入了CompletableFuture 类;

实现异步API

技能点:

  1. 提供异步API;
  2. 修改同步的API为异步的API,如何使用流水线把两个任务合并为一个异步计算操作;
  3. 响应式的方式处理异步操作的完成事件;
类型 区别 是否堵塞
同步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

里面的实现细节后面单独成文章再讲。

小结

  1. 执行一些比较耗时的操作,尤其是依赖一个或者多个远程服务的操作,可以使用异步任务改善程序的性能,加快程序的响应速度;
  2. 使用CompletableFuture你可以轻松的实现异步API;
  3. CompletableFuture提供了异常管理机制,让主线程有机会接管子任务抛出的异常;
  4. 把同步API封装到CompletableFuture中,可以异步得到它的结果;
  5. 如果异步任务之间互相独立,而他们之间的某一些结果是另外一些的输入,可以把这些任务进行compose;
  6. 可以为CompletableFuture中的任务注册一个回调函数,当任务执行完毕之后再进行一些其它操作;
  7. 你可以决定什么时候结束程序的运行,是所有的CompletableFuture任务所有对象执行完毕,或者只要其中任何一个完成即可。

原创不易,转载请注明出处。

java8-CompleableFuture的使用1的更多相关文章

  1. Java8实战分享

    虽然很多人已经使用了JDK8,看到不少代码,貌似大家对于Java语言or SDK的使用看起来还是停留在7甚至6. Java8在流式 or 链式处理,并发 or 并行方面增强了很多,函数式的风格使代码可 ...

  2. java8中lambda表达式的应用,以及一些泛型相关

    语法部分就不写了,我们直接抛出一个实际问题,看看java8的这些新特性究竟能给我们带来哪些便利 顺带用到一些泛型编程,一切都是为了简化代码 场景: 一个数据类,用于记录职工信息 public clas ...

  3. Android Studio2.1.2 Java8环境下引用Java Library编译出错

    转载请注明出处:http://www.cnblogs.com/LT5505/p/5685242.html 问题:在Android Studio2.1.2+Java8的环境下,引用Java Librar ...

  4. Java笔记——Java8特性之Lambda、方法引用和Streams

    Java8已经推出了好一段时间了,而掌握Java8的新特性也是必要的,如果要进行Spring开发,那么可以发现Spring的官网已经全部使用Java8来编写示例代码了,所以,不学就看不懂. 这里涉及三 ...

  5. 关于Java8函数式编程你需要了解的几点

    函数式编程与面向对象的设计方法在思路和手段上都各有千秋,在这里,我将简要介绍一下函数式编程与面向对象相比的一些特点和差异. 函数作为一等公民 在理解函数作为一等公民这句话时,让我们先来看一下一种非常常 ...

  6. Java8并发教程:Threads和Executors

    来之:ImportNew 欢迎阅读我的Java8并发教程的第一部分.这份指南将会以简单易懂的代码示例来教给你如何在Java8中进行并发编程.这是一系列教程中的第一部分.在接下来的15分钟,你将会学会如 ...

  7. java8 学习系列--NIO学习笔记

    近期有点时间,决定学习下java8相关的内容: 当然了不止java8中新增的功能点,整个JDK都需要自己研究的,不过这是个漫长的过程吧,以自己的惰性来看: 不过开发中不是有时候讲究模块化开发么,那么我 ...

  8. Java8函数式编程

    在Java8的 java.util.function中包含以下几个接口 1.Function,先上源码 /* * Copyright (c) 2010, 2013, Oracle and/or its ...

  9. Java8闭包

    闭包在很多语言中都存在,例如C++,C#.闭包允许我们创建函数指针,并把它们作为参数传递,Java编程语言提供了接口的概念,接口中可以定义抽象方法,接口定义了API,并希望用户或者供应商来实现这些方法 ...

  10. Java8新特性——接口的默认方法和类方法

    Java8新增了接口的默认方法和类方法: 以前,接口里的方法要求全部是抽象方法,java8以后允许在接口里定义默认方法和类方法: 不同的是: 默认方法可以通过实现接口的类实例化的对象来调用,而类方法只 ...

随机推荐

  1. Rabbitmq-单机安装

    Rabbitmq介绍   官网地址:https://www.rabbitmq.com RabbitMQ是一款在全球范围内使用非常广泛的开源消息队列中间件.它轻量级.易部署.并支持多种协议.它基于Erl ...

  2. 小胖求学系列之-文档生成利器(上)-smart-doc

    最近小胖上课总是挂着黑眼圈,同桌小张问:你昨晚通宵啦?小胖有气无力的说到:最近开发的项目接口文档没写,昨晚补文档补了很久,哎,昨晚只睡了2个小时.小张说:不是有生成文档工具吗,类似swagger2.s ...

  3. 基于ATxmega128的ASF串口应用

    1.编辑串口的配置参数,一般将这些参数放在conf_usart.h配置头文件中,本程序将这些参数放在user_board.h头文件中 #define USART_SERIAL &USARTD0 ...

  4. html5+css3的神奇搭配

    1.关于浮动 浮动的元素会脱离标准文档流(float),从而不占据空间,实现了一行排列多个元素的效果 ,但是又导致上级元素height消失,处理这种情况的方法就是有两种: 1.第一种在css里写个伪类 ...

  5. XAF Architecture XAF架构

    Applications built with the eXpressApp Framework are comprised of several functional blocks. The dia ...

  6. oop面向对象【接口、多态】

    今日内容 1.接口 2.三大特征——多态 3.引用类型转换 教学目标 1.写出定义接口的格式 2.写出实现接口的格式 3.说出接口中成员的特点 4.能够说出使用多态的前提条件 5.理解多态的向上转型 ...

  7. Netty如何监控内存泄露

    目录 Netty如何监控内存泄露 前言 JDK的弱引用和引用队列 Netty的实现思路 代码实现 分配监控对象 追踪和检查泄露 Netty如何监控内存泄露 前言 一般而言,在Netty程序中都会采用池 ...

  8. 【.net core 入坑】.net core 3.0 报错:在 FETCH 语句中选项 NEXT 的用法无效

    目录 1.事故现场: 2.分析及解决方案: 1.事故现场: 在项目中使用.net core 3.0,在EF链接sqlserver2008,在程序中使用了分页用的skip和take,程序报错: 在 FE ...

  9. Maven项目使用mybatis报错 org.apache.ibatis.binding.BindingException: Invalid bound statement (not found):

    maven项目使用mybatis时,找不到mapper文件(.xml) 错误信息提示: 项目可以正常运行,但是在有请求到达服务器时(有访问数据库的请求),会出现报错!! 错误原因: mybatis没有 ...

  10. Linux命令学习-cat命令

    Linux中,cat命令的全称是concatenate,主要用于显示文件内容. 查看centos系统版本 cat /etc/centos-release 查看文件 gogs.log 的内容 cat g ...