文章目录

  • 引言
  • 生活中的例子
    • 场景1
    • 场景2
  • Java中的Future
    • 如何获取Future
    • Future的主要方法及使用
    • Future的核心源码
  • Future模式的高阶版本—— CompletableFuture
    • 如何获取CompletableFuture
    • CompletableFuture的主要方法及使用
  • 小结

引言

在高性能编程中,并发编程已经成为了极为重要的一部分。并发编程可以总结为三个核心问题:分工、同步和互斥。编写并发程序,首先要做的就是分工,所谓分工指的是如何高效地拆解任务并分配给线程。由于并发编程比串行编程更困难,也更容易出错,因此,我们就更需要借鉴一些前人优秀的,成熟的设计模式,使得我们的设计更加健壮,更加完美。
而Future模式,正是其中使用最为广泛,也是极为重要的一种设计模式。今天就跟少侠了解一手Future模式!

生活中的例子

场景1

小张喜欢没事泡泡茶,每次都是洗水壶–>洗茶壶–>洗茶杯–>烧开水–>拿茶叶–>泡茶,如下图,喝到茶大概得花上20分钟。

场景2

但是小王不这么干,对于烧水泡茶这个程序,她采取的方案是下图所示的这样:用两个线程T1和T2来完成烧水泡茶程序,T1负责洗水壶、烧开水、泡茶这三道工序,T2负责洗茶壶、洗茶杯、拿茶叶三道工序,其中T1在执行泡茶这道工序时需要等待T2完成拿茶叶的工序。对于T1的这个等待动作,你应该可以想出很多种办法,例如Thread.join()、CountDownLatch,甚至阻塞队列都可以解决,不过今天我们用Future特性来实现。

Java中的Future

如何获取Future

// 提交Runnable任务
Future submit(Runnable task);

这个方法的参数是一个Runnable接口,Runnable接口的run()方法是没有返回值的,所以 submit(Runnable task) 这个方法返回的Future仅可以用来断言任务已经结束了,类似于Thread.join()。

// 提交Callable任务Future submit(Callable task);

这个方法的参数是一个Callable接口,它只有一个call()方法,并且这个方法是有返回值的,所以这个方法返回的Future对象可以通过调用其get()方法来获取任务的执行结果。

// 提交Runnable任务及结果引用  Future submit(Runnable task, T result);

这个方法很有意思,假设这个方法返回的Future对象是future,future.get()的返回值就是传给submit()方法的参数result。这个方法该怎么用呢?下面这段示例代码展示了它的经典用法。需要你注意的是Runnable接口的实现类Task声明了一个有参构造函数 Task(Result r) ,创建Task对象的时候传入了result对象,这样就能在类Task的run()方法中对result进行各种操作了。result相当于主线程和子线程之间的桥梁,通过它主子线程可以共享数据。

Future的主要方法及使用

获取到Future之后,我们怎么来进行使用呢,Java中提供了如下几个核心方法:

 // 取消任务boolean cancel(boolean mayInterruptIfRunning);// 判断任务是否已取消  boolean isCancelled();// 判断任务是否已结束boolean isDone();// 获得任务执行结果get();// 获得任务执行结果,支持超时get(long timeout, TimeUnit unit);

那么如何灵活的使用这几个方法呢?下面的示例代码就是用这一节提到的Future特性来实现的。首先,我们创建了两个Future——task1和task2,task1完成洗水壶、烧开水、泡茶的任务,task2完成洗茶壶、洗茶杯、拿茶叶的任务;这里需要注意的是task1这个任务在执行泡茶任务前,需要等待task2把茶叶拿来,所以task1内部需要引用task2,并在执行泡茶之前,调用task2的get()方法实现等待。

一次执行结果:

thread13--->洗茶壶
thread14--->洗水壶
thread13--->洗茶杯
thread14--->烧开水
thread13--->拿茶叶
thread14--->泡茶
泡茶吧!来一杯大红袍!
elapsed: 16秒

Future的核心源码

那么Future又是如何实现异步操作的呢,我们结合源码来看一下。

由于Future是接口,这里我们主要看它的实现类FutureTask的实现。关键的部分在下面,FutureTask作为一个线程单独执行时,会将结果保存到Object类型的变量outcome中,并设置任务的状态,下面是FutureTask的run()方法:

从FutureTask中获得结果的实现如下:

Future模式的高阶版本—— CompletableFuture

Future模式虽然好用,但也有一个问题,那就是将任务提交给线程后,调用线程并不知道这个任务什么时候执行完,如果执行调用get()方法或者isDone()方法判断,可能会进行不必要的等待,那么系统的吞吐量很难提高。
为了解决这个问题,JDK对Future模式又进行了加强,创建了一个CompletableFuture,它可以理解为Future模式的升级版本,它最大的作用是提供了一个回调机制,可以在任务完成后,自动回调一些后续的处理,这样,整个程序可以把“结果等待”完全给移除了。

如何获取CompletableFuture

public static CompletableFuture<Void> runAsync(Runnable runnable)
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)

四个静态方法用来为一段异步执行的代码创建CompletableFuture对象,方法的参数类型都是函数式接口,所以可以使用lambda表达式实现异步任务

  • runAsync方法:它以Runnable函数式接口类型为参数,所以CompletableFuture的计算结果为空。

  • supplyAsync方法以Supplier函数式接口类型为参数,CompletableFuture的计算结果类型为U。

说明:Async结尾的方法都是可以异步执行的,如果指定了线程池,会在指定的线程池中执行,如果没有指定,默认会在ForkJoinPool.commonPool()中执行。

CompletableFuture的主要方法及使用

关于CompletableFuture,Java中提供了如下几个核心方法:
1 变换结果
由于回调风格的实现,我们不必因为等待一个计算完成而阻塞着调用线程,而是告诉CompletableFuture当计算完成的时候请执行某个Function。还可以串联起来。
这些方法的输入是上一个阶段计算后的结果,返回值是经过转化后结果:

public <U> CompletionStage<U> thenApply(Function<? super T,? extends U> fn);
public <U> CompletionStage<U> thenApplyAsync(Function<? super T,? extends U> fn);
public <U> CompletionStage<U> thenApplyAsync(Function<? super T,? extends U> fn,Executor executor);

2 消费结果
这些方法只是针对结果进行消费,入参是Consumer,没有返回值:

public CompletionStage<Void> thenAccept(Consumer<? super T> action);
public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action);
public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action,Executor executor);

3 计算结果完成时的处理
当CompletableFuture的计算结果完成,或者抛出异常的时候,可以执行特定的Action。主要是下面的方法:

public CompletableFuture<T> whenComplete(BiConsumer<? super T,? super Throwable> action)
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action)
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action, Executor executor)
public CompletableFuture<T> exceptionally(Function<Throwable,? extends T> fn)

4 结合两个CompletionStage的结果,进行转化后返回
需要上一阶段的返回值,并且other代表的CompletionStage也要返回值之后,把这两个返回值,进行转换后返回指定类型的值。

public <U,V> CompletionStage<V> thenCombine(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn);
public <U,V> CompletionStage<V> thenCombineAsync(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn);
public <U,V> CompletionStage<V> thenCombineAsync(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn,Executor executor);

为了和大家一起体会CompletableFuture异步编程的优势,这里我们用CompletableFuture重新实现前面曾提及的烧水泡茶程序。首先还是需要先完成分工方案,在下面的程序中,我们分了3个任务:任务1负责洗水壶、烧开水,任务2负责洗茶壶、洗茶杯和拿茶叶,任务3负责泡茶。其中任务3要等待任务1和任务2都完成后才能开始。这个分工如下图所示。

下面是具体代码实现,你先略过runAsync()、supplyAsync()、thenCombine()这些不太熟悉的方法,从大局上看,你会发现:

  • 无需手工维护线程,没有繁琐的手工维护线程的工作,给任务分配线程的工作也不需要我们关注;
  • 语义更清晰,例如 f3 = f1.thenCombine(f2, ()->{}) 能够清晰地表述“任务3要等待任务1和任务2都完成后才能开始”;
  • 代码更简练并且专注于业务逻辑,几乎所有代码都是业务逻辑相关的。

小结

今天我们主要介绍Future模式,我们从一个最简单的Future模式开始,逐步深入,先后介绍了JDK内部的Future模式实现,以及对Future模式的进化版本CompletableFuture做了简单的介绍。对
于多线程开发而言,Future模式的应用极其广泛,可以说这个模式已经成为了异步开发的基础设施。

【Java并发编程实战】(十七):Future和CompletableFuture的原理及实战——异步编程没有那么难相关推荐

  1. 并行编程-disruptor与Future(CompletableFuture 和 guava)场景比较

    Disruptor 是 LMAX 公司开发的高性能队列,用于解决内存队列的延迟问题. LMAX 基于 Disruptor 打造的系统单线程能支撑每秒 600 万订单,许多著名的开源项目也使用 Disr ...

  2. Java并发(十九):final实现原理

    final在Java中是一个保留的关键字,可以声明成员变量.方法.类以及本地变量. 一旦你将引用声明作final,你将不能改变这个引用了,编译器会检查代码,如果你试图将变量再次初始化的话,编译器会报编 ...

  3. java模拟退火算法求函数_模拟退火算法从原理到实战【基础篇】

    模拟退火算法来源于固体退火原理,将固体加温至充分高,再让其徐徐冷却,加温时,固体内部粒子随温升变为无序状,内能增大,而徐徐冷却时粒子渐趋有序,在每个温度都达到平衡态,最后在常温时达到基态,内能减为最小 ...

  4. 阿里P9“墙裂”推荐,Docker实战技术文档,从原理到实战

    刚刚入行还是新人的你,有没有被面试过容器化技术呢? 是否又被问过Docker工作原理? 如今的面试就好像是造火箭,面试官希望你能够掌握其中至少8到10个 甚至更多的技术! 什么是Docker? 在Do ...

  5. 人工智能AI实战100讲(七)-原理+代码实战 | 双目视觉中的极线校正

    为什么要做极线校正? 三维重建是通过双目立体匹配实现的如图1,通过匹配空间中点在两个图像中的投影点,再根据三角关系得到P的Z值. 我们双目相机拍摄的时候实际情况下如下图a,两个图像做匹配时如我们图中蓝 ...

  6. 【并发编程】异步编程CompletableFuture实战

    文章目录 1.CompletableFuture简介 2.CompletableFuture核心API实战 3.CompletableFuture嵌套案例实战 4.合并两个CompletableFut ...

  7. 淘宝资深java技术专家整理分享java异步编程实战文档

    前言 本文由淘宝资深java技术专家爆肝整理分享的java异步编程实战文档,针对常见异步编程场景,从编程语言.开发框架等角度深入讲解异步编程的原理和方法,每个技术点都附有案例代码! 通常Java开发人 ...

  8. Java 并发编程入门

    JUC 写在前面 推荐阅读 http://ifeve.com/java-memory-model-1/ :深入理解 Java 内存模型-程晓明 https://segmentfault.com/a/1 ...

  9. 认识Java异步编程

    一 .认识异步编程 通常Java开发人员喜欢使用同步代码编写程序,因为这种请求(request)/响应(response)的方式比较简单,并且比较符合编程人员的思维习惯;这种做法很好,直到系统出现性能 ...

最新文章

  1. Java 线程 笔记(1)
  2. valid, satisfiable, unsatisfiable的例子
  3. Leetcode 565. Array Nesting
  4. java输出日志_java代码中如何正确使用loggger日志输出
  5. Dubbo(十三)dubbo的负载均衡配置策略
  6. layui如何存在多个弹窗_layer重复弹出(layui弹层同时存在多个)的解决方法_心善_前端开发者...
  7. S32DS for PowrPC中对dcc编译器的支持
  8. 计算机组成原理—高速缓冲存储器
  9. c#数据库事务锁类型
  10. Topaz Video Enhance Al视频无损放大软件常见问题指南
  11. 改版后的CSDN如何更换皮肤
  12. python 判断大于等于且小于等于_关于if语句:检查python中值是否大于、小于或等于零的更好方法...
  13. JAVA NIO文件映射、通道、流读写文件示例
  14. iphone12绿色好看 ,相比被全民吐槽的蓝色,绿色是怎么做到零差评的
  15. UML 之 C++类图关系全面剖析
  16. 百度云其实真的很不错
  17. CES展会的技术亮点奠定2017年各个行业的基调
  18. 测试测试是打卡机坑了大家
  19. BDD之单元测试(一):BDD出现的背景
  20. centos安装apche

热门文章

  1. 网络云存储技术Windows server 2012 (项目二十 一 基于Cluster的高可用企业WEB服务器的部署)
  2. 阿里巴巴大规模神龙裸金属 Kubernetes 集群运维实践
  3. CUBA China 最新进展
  4. 「手把手教你」用Python量化海龟交易法则
  5. VC对11类NFT初创企业的看法与建议
  6. hdu6082 度度熊与邪恶大魔王
  7. 自我管理数据缓冲区内存
  8. Ubuntu 22.04 无法使用网易云音乐
  9. java-php-python-ssm-旅游系统-计算机毕业设计
  10. 小米路由器AX3600开启ssh