有情怀,有干货,微信搜索【三太子敖丙】关注这个有一点点东西的程序员。

本文 GitHub https://github.com/JavaFamily 已收录,有一线大厂面试完整考点、资料以及我的系列文章。

前言

在高性能编程中,并发编程已经成为了极为重要的一部分。在单核CPU性能已经趋于极限时,我们只能通过多核来进一步提升系统的性能,因此就催生了并发编程。

由于并发编程比串行编程更困难,也更容易出错,因此,我们就更需要借鉴一些前人优秀的,成熟的设计模式,使得我们的设计更加健壮,更加完美。

而Future模式,正是其中使用最为广泛,也是极为重要的一种设计模式。今天就跟阿丙了解一手Future模式!

生活中的Future模式

为了更快的了解Future模式,我们先来看一个生活中的例子。

场景1:

午饭时间到了,同学们要去吃饭了,小王下楼,走了20分钟,来到了肯德基,点餐,排队,吃饭一共花了20分钟,又花了20分钟走回公司继续工作,合计1小时。

场景2

午饭时间到了,同学们要去吃饭了,小王点了个肯德基外卖,很快,它就拿到了一个订单(虽然订单不能当饭吃,但是有了订单,还怕吃不上饭嘛)。接着小王可以继续干活,30分钟后,外卖到了,接着小王花了10分钟吃饭,接着又可以继续工作了,成功的卷到了隔壁的小汪。

很明显,在这2个场景中,小王的工作时间更加紧凑,特别是那些排队的时间都可以让外卖员去干,因此可以更加专注于自己的本职工作。聪明的你应该也已经体会到了,场景1就是典型的函数同步调用,而场景2是典型的异步调用。

而场景2的异步调用,还有一个特点,就是它拥有一个返回值,这个返回值就是我们的订单。这个订单很重要,凭借着这个订单,我们才能够取得当前这个调用所对应的结果。

这里的订单就如同Future模式中的Future,这是一个合约,一份承诺。虽然订单不能吃,但是手握订单,不怕没吃的,虽然Future不是我们想要的结果,但是拿着Future就能在将来得到我们想要的结果。

因此,Future模式很好的解决了那些需要返回值的异步调用。

Future模式中的主要角色

一个典型的Future模式由以下几个部分组成:

  • Main:系统启动,调用Client发出请求
  • Client:返回Data对象,立即返回FutureData,并开启ClientThread线程装配RealData
  • Data:返回数据的接口
  • FutureData:Future数据,构造很快,但是是一个虚拟的数据,需要装配RealData,好比一个订单
  • RealData:真实数据,其构造是比较慢的,好比上面例子中的肯德基午餐。

它们之间的相互关系如下图:

其中,值得注意是Data,RealData和FutureData。这是一组典型的代理模式,Data接口表示对外数据,RealData表示真实的数据,就好比午餐,获得它的成本比较高,需要很多时间;相对的FutureData作为RealData的代理,类似于一个订单/契约,通过FutureData,可以在将来获得RealData。

因此,Future模式本质上是代理模式的一种实际应用。

实现一个简单的Future模式

根据上面的设计,让我们来实现一个简单的代理模式吧!

首先是Data接口,代表数据:

public interface Data {public String getResult ();
}

接着是FutureData,也是整个Future模式的核心:

public class FutureData implements Data {// 内部需要维护RealDataprotected RealData realdata = null;          protected boolean isReady = false;public synchronized void setRealData(RealData realdata) {if (isReady) { return;}this.realdata = realdata;isReady = true;//RealData已经被注入,通知getResult()notifyAll();                                        }//会等待RealData构造完成public synchronized String getResult() {          while (!isReady) {try {//一直等待,直到RealData被注入wait();                                   } catch (InterruptedException e) {}}//真正需要的数据从RealData获取return realdata.result;                         }
}

下面是RealData:

public class RealData implements Data {protected final String result;public RealData(String para) {StringBuffer sb=new StringBuffer();//假设这里很慢很慢,构造RealData不是一个容易的事result =sb.toString();}public String getResult() {return result;}
}

然后从Client得到Data:

public class Client {//这是一个异步方法,返回的Data接口是一个Futurepublic Data request(final String queryStr) {final FutureData future = new FutureData();new Thread() {                                      public void run() {                     // RealData的构建很慢,所以在单独的线程中进行RealData realdata = new RealData(queryStr);//setRealData()的时候会notify()等待在这个future上的对象future.setRealData(realdata);}                                               }.start();// FutureData会被立即返回,不会等待RealData被构造完return future;                               }
}

最后一个Main函数,把所有一切都串起来:

public static void main(String[] args) {Client client = new Client();//这里会立即返回,因为得到的是FutureData而不是RealDataData data = client.request("name");System.out.println("请求完毕");try {//这里可以用一个sleep代替了对其他业务逻辑的处理//在处理这些业务逻辑的过程中,RealData被创建,从而充分利用了等待时间Thread.sleep(2000);} catch (InterruptedException e) {}//使用真实的数据,如果到这里数据还没有准备好,getResult()会等待数据准备完,再返回System.out.println("数据 = " + data.getResult());
}

这是一个最简单的Future模式的实现,虽然简单,但是已经包含了Future模式中最精髓的部分。对大家理解JDK内部的Future对象,有着非常重要的作用。

Java中的Future模式

Future模式是如此常用,在JDK内部已经有了比较全面的实现和支持。下面,让我们一起看看JDK内部的Future实现:

首先,JDK内部有一个Future接口,这就是类似前面提到的订单,当然了,作为一个完整的商业化产品,这里的Future的功能更加丰富了,除了get()方法来获得真实数据以外,还提供一组辅助方法,比如:

  • cancel():如果等太久,你可以直接取消这个任务
  • isCancelled():任务是不是已经取消了
  • isDone():任务是不是已经完成了
  • get():有2个get()方法,不带参数的表示无穷等待,或者你可以只等待给定时间

下面代码演示了这个Future的使用方法:

        //异步操作 可以用一个线程池ExecutorService executor = Executors.newFixedThreadPool(1);//执行FutureTask,相当于上例中的 client.request("name") 发送请求//在这里开启线程进行RealData的call()执行Future<String> future = executor.submit(new RealData("name"));System.out.println("请求完毕,数据准备中");try {//这里依然可以做额外的数据操作,这里使用sleep代替其他业务逻辑的处理Thread.sleep(2000);} catch (InterruptedException e) {}//如果此时call()方法没有执行完成,则依然会等待System.out.println("数据 = " + future.get());

整个使用过程非常简单,下面我们来分析一下executor.submit()里面究竟发生了什么:

    public <T> Future<T> submit(Callable<T> task) {if (task == null) throw new NullPointerException();// 根据Callable对象,创建一个RunnableFuture,这里其实就是FutureTaskRunnableFuture<T> ftask = newTaskFor(task);//将ftask推送到线程池//在新线程中执行的,就是run()方法,在下面的代码中有给出execute(ftask);//返回这个Future,将来通过这个Future就可以得到执行的结果return ftask;}protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {return new FutureTask<T>(callable);}

最关键的部分在下面,FutureTask作为一个线程单独执行时,会将结果保存到outcome中,并设置任务的状态,下面是FutureTask的run()方法:

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

    public V get() throws InterruptedException, ExecutionException {int s = state;//如果没有完成,就等待,回到用park()方法阻塞线程//同时,所有等待线程会在FutureTask的waiters字段中排队等待if (s <= COMPLETING)s = awaitDone(false, 0L);return report(s);}private V report(int s) throws ExecutionException {//outcome里保存的就是最终的计算结果Object x = outcome;if (s == NORMAL)//正常完成,就返回outcomereturn (V)x;//如果没有正常完成, 比如被用户取消了,或者有异常了,就抛出异常if (s >= CANCELLED)throw new CancellationException();throw new ExecutionException((Throwable)x);}

Future模式的高阶版本—— CompletableFuture

Future模式虽然好用,但也有一个问题,那就是将任务提交给线程后,调用线程并不知道这个任务什么时候执行完,如果执行调用get()方法或者isDone()方法判断,可能会进行不必要的等待,那么系统的吞吐量很难提高。

为了解决这个问题,JDK对Future模式又进行了加强,创建了一个CompletableFuture,它可以理解为Future模式的升级版本,它最大的作用是提供了一个回调机制,可以在任务完成后,自动回调一些后续的处理,这样,整个程序可以把“结果等待”完全给移除了。

下面来看一个简单的例子:

在这个例子中,首先以getPrice()为基础创建一个异步调用,接着,使用thenAccept()方法,设置了一个后续的操作,也就是当getPrice()执行完成后的后续处理。

不难看到,CompletableFuture比一般的Future更具有实用性,因为它可以在Future执行成功后,自动回调进行下一步的操作,因此整个程序不会有任何阻塞的地方(也就是说你不用去到处等待Future的执行,而是让Future执行成功后,自动来告诉你)。

以上面的代码为例,CompletableFuture之所有会有那么神奇的功能,完全得益于AsyncSupply类(由上述代码中的supplyAsync()方法创建)。

AsyncSupply在执行时,如下所示:

        public void run() {CompletableFuture<T> d; Supplier<T> f;if ((d = dep) != null && (f = fn) != null) {dep = null; fn = null;if (d.result == null) {try {//这里就是你要执行的异步方法//结果会被保存下来,放到d.result字段中d.completeValue(f.get());} catch (Throwable ex) {d.completeThrowable(ex);}}//执行成功了,进行后续处理,在这个后续处理中,就会调用thenAccept()中的消费者//这里就相当于Future完成后的通知d.postComplete();}}

继续看d.postComplete(),这里会调用后续一系列操作

   final void postComplete() {//省略部分代码,重点在tryFire()里//在tryFire()里,真正触发了后续的调用,也就是thenAccept()中的部分f = (d = h.tryFire(NESTED)) == null ? this : d;}}}

絮叨

今天,我们主要介绍Future模式,我们从一个最简单的Future模式开始,逐步深入,先后介绍了JDK内部的Future模式实现,以及对Future模式的进化版本CompletableFuture做了简单的介绍。对

于多线程开发而言,Future模式的应用极其广泛,可以说这个模式已经成为了异步开发的基础设施。

好啦如果想了解多线程的更多知识点可以关注我,查看历史文章,我也会持续更新的。


敖丙把自己的面试文章整理成了一本电子书,共 1630页!

干货满满,字字精髓。目录如下,还有我复习时总结的面试题以及简历模板,现在免费送给大家。

链接:https://pan.baidu.com/s/1ZQEKJBgtYle3v-1LimcSwg 密码:wjk6

我是敖丙,你知道的越多,你不知道的越多,感谢各位人才的:点赞收藏评论,我们下期见!


文章持续更新,可以微信搜一搜「 三太子敖丙 」第一时间阅读,回复【资料】有我准备的一线大厂面试资料和简历模板,本文 GitHub https://github.com/JavaFamily 已经收录,有大厂面试完整考点,欢迎Star。

JAVA Future类详解相关推荐

  1. java并发编程Future类详解

    作用和举例 future类的作用就是为了调用其他线程完成好后的结果,再返回到当前线程中,如上图举例: 小王自己是主线程,叫外卖等于使用future类,叫好外卖后小王就接着干自己的事去了,当外卖到了的时 ...

  2. Java Pattern类详解

    Java Pattern类详解 Pattern在java.util.regex包中,是正则表达式的编译表示形式,此类的实例是不可变的,可供多个并发线程安全使用. 定义 public final cla ...

  3. java Random类详解

    2019独角兽企业重金招聘Python工程师标准>>> Random类 (java.util) Random类中实现的随机算法是伪随机,也就是有规则的随机.在进行随机时,随机算法的起 ...

  4. java file类详解_Java File类详解及IO介绍及使用

    我是一个CPU:这个世界慢!死!了!,可以感受到CPU,内存和硬盘之间的速度比较. 文件的本质 OS(操作系统)的目的就是可以让上层程序可以通过一种统一的方式去拿到无论什么磁盘里的文件(即一段字节流) ...

  5. Java File类详解

    在 Java 中,File 类是 java.io 包中唯一代表磁盘文件本身的对象,也就是说,如果希望在程序中操作文件和目录,则都可以通过 File 类来完成.File 类定义了一些方法来操作文件,如新 ...

  6. Java System类详解

    System 类位于 java.lang 包,代表当前 Java 程序的运行平台,系统级的很多属性和控制方法都放置在该类的内部.由于该类的构造方法是 private 的,所以无法创建该类的对象,也就是 ...

  7. Java Object类详解

    Object 是 Java 类库中的一个特殊类,也是所有类的父类.也就是说,Java 允许把任何类型的对象赋给 Object 类型的变量.当一个类被定义后,如果没有指定继承的父类,那么默认父类就是 O ...

  8. Java—File类详解及实践

    关注微信公众号:CodingTechWork,一起学习进步. File类介绍 File类概述   File类是java.io包下代表与平台无关的文件和目录.File可以新建.删除.重命名文件和目录,但 ...

  9. Java Scaner类详解_动力节点Java学院整理

    Java.util.Scanner是Java5.0的新特征,主要功能是简化文本扫描.这个类最实用的地方表现在获取控制台输入,其他的功能都很鸡肋,尽管Java API文档中列举了大量的API方法,但是都 ...

  10. Java Entry类详解

    Entry类概述 Java的entry是一个静态内部类,实现Map.Entry< K ,V> 这个接口,通过entry类可以构成一个单向链表. 一.java中Map及Map.Entry ( ...

最新文章

  1. 解决Titanium Tab组件click事件在iOS中不生效的方案
  2. My first syscall
  3. python去掉txt文件行尾换行
  4. WAS6集群部署及初步测试
  5. OAG – WhoIsWho 同名消歧竞赛发布 | 10万元奖金双赛道
  6. mysql function函数_详解MySQL如何按表创建千万级的压测数据
  7. 如何制作自动更新程序?
  8. 电脑上的linux是什么文件夹,linux删除文件夹,详细教您电脑的linux怎么样删除文件夹...
  9. 统计代码行数的方法梳理
  10. 【maven】配置ali镜像
  11. #pragma warning(disable 4786)
  12. jpg格式的矢量化arcgis_arcgis将jpg矢量化,带坐标导出TIFF格式,再导入cad的方法...
  13. 高端大气上档次!10个精美的国外HTML5网站欣赏
  14. 【C语言】size与strlen的区别解析
  15. EasyPoi word导出教程
  16. cad放大_cad快捷键大全amp;鼠标各键用法
  17. 某某读书搜索__DATA__分析
  18. NBOJv2 Problem 1009 蛤玮的魔法(二分)
  19. mysql缓冲和缓存设置
  20. carsim学习日记1

热门文章

  1. win10c盘扩容_如何给磁盘进行扩容/拆分/合并的操作?保姆级教学
  2. 人生之路优化,bug修复
  3. 台式计算机英特尔时间同步,我电脑时间没法与Inter同步,?
  4. 利用 adb 对手机进行屏幕分辨率设置
  5. Campass + Scss ,让我们更优雅的编写CSS
  6. Python 判断素数(质数)的方法讲解
  7. Nacos——Distro一致性协议(架构篇)
  8. 抖音短视频系统开发Service后台播放音乐实例
  9. jav中什么是组织java程序_在Java中,开发图形用户界面的程序需要使用一个系统提供的类库,这个类库就是以下的______包。A.jav...
  10. 2020认证杯第二阶段选提建议