第9章-Future

9.1 模式简介

  1. 假设我们去蛋糕店买蛋糕。下单后,店员一边递给我们提货单,一边说 “请您傍晚再来取蛋糕”。到了傍晚,我们就拿着提货单去取蛋糕。这时,店员会先和我们说 “您的蛋糕已经做好了”,然后将蛋糕递给了我们。
  2. Future的意思是未来、期货(经济学用语)。假设有一个方法需要花费很长时间才能获取运行结果。那么,与其一直等待结果,不如先拿一张 “提货单”。获取提货单并不耗费时间。这里的 “提货单” 我们就称为 Future 角色。
  3. 获取 Future 角色的线程会在稍后使用 Future 角色来获取运行结果。这与凭着提货单去取蛋糕非常相似。如果运行结果已经出来了,那么直接领取即可;如果运行结果还没有出来,那么需要等待结果出来。
  4. Future角色是购买蛋糕时的提货单、预购单、预约券,是 “未来” 可以转化为实物的凭证。

9.3 Future模式中的角色

9.3.1 Client(请求者)

  1. Client角色向 Host 角色发出请求(request),并会立即接收到请求的处理结果(返回值)——VirtualData角色。
  2. 不过,这里接收到的 VirtualData 角色实际上是Future角色。也就是说,Future角色戴上了VirtualData角色的面具。Client角色没有必要知道返回值究竟是 RealData 角色还是 Future 角色。稍后,Client角色会通过VirtualData角色来进行操作。

9.3.2 Host

  1. Host角色会创建新的线程,并开始在新线程中创建RealData角色。同时,它会将Future角色(当作VirtualData角色)返回给Client角色。
  2. 新线程在创建了RealData角色后,会将其设置到Future角色中。

9.3.3 VirtualData(虚拟数据)

  1. VirtualData角色是让 Future 角色与 RealData 角色具有一致性的角色。在示例程序中,由 Data 接口扮演此角色

9.3.4 RealData(真实数据)

  1. RealData角色是表示真实数据的角色。创建该对象需要花费很多时间。

9.3.5 Future(期货)

  1. Future角色是RealData角色的 “提货单”,由 Host 角色传递给 Client 角色。从程序行为上看,对Client角色而言,Future角色就是 VirtualData 角色。实际上,当Client角色操作Future角色时,线程会调用 wait 方法等待,直至 RealData 角色创建完成。
  2. 但是,一旦RealData角色创建完成,线程就不会再继续等待。Future 角色会将Client角色的操作委托给RealData角色。

9.3.6 类图

类图

9.4 拓展思路的要点

9.4.1 吞吐量会提高吗

  1. 使用Thread-Per-Message模式(第7章)时,我们没有得到处理结果。但是,使用Future模式的话,不仅能保留Thread-Per-Message模式中 “提高程序响应性” 这个优点,还能够获取处理结果。
  2. 不过,可能大家会有这样的质疑:“我承认Future模式提高了程序响应性,但是它并没有提高吞吐量。如果是从获取到所有结果的最后时间来考虑,无论是单线程运行也好,多线程运行也罢,获取所有结果花费的时间都是相同的。那么,即使将程序修改为多线程运行也无法减少总的处理时间。”
  3. 这个质疑一半是正确的,一半是错误的。诚然,即使将程序修改为多线程运行,也无法减少总的处理时间。而问题是 “负责长时间处理的线程是哪个线程”。
  4. 在单CPU服务器上的 Java虚拟机中,如果只是进行多线程计算,吞吐量是无法提高的。这是因为即使让多个线程分担计算任务,运行计算的也只有一个CPU而已。但是,如果结合输入输出(I/O)一起考虑,情况就不同了。例如,当程序在进行磁盘读写时,并不是全部操作都由CPU负责。在程序进行磁盘读写操作时,CPU只是处于等待状态。这时,CPU是有 “空闲时间” 的。如果可以将这些空闲时间分配给其他线程,让它们先进行处理,就可以提高吞吐量。

9.4.2 异步方法调用的 “返回值”

  1. Java的方法调用全部都是同步的(synchronous)。即一旦调用了某个方法,只有等待该方法执行完毕后才能继续向前执行。
  2. Thread-Per-Thread模式通过在方法中创建一个新的线程,模拟实现了异步(asynchronous)方法调用。当然,无论怎样使用Thread-Per-Thread模式,Java的方法调用本身仍然是同步的。但是,即便被调用的方法的处理没有全部终止,调用方的处理依然可以继续向前执行,这就是模拟实现异步调用的意义。
  3. 只使用Thread-Per-Thread模式是无法获取处理结果(即异步方法调用的“返回值”)的。我们可以通过使用Future模式来 “稍后设置处理结果”,从而操作异步方法调用的 “返回值”。

9.4.3 “准备返回值” 和 “使用返回值” 的分离

  1. 使用 Future 模式可以将 “准备方法的返回值” 与 “使用方法的返回值” 分离开来。接着,我们就可以在其他线程中分别处理 “准备返回值” 和 “使用返回值” 了。
  2. 将伴随方法调用的一连串处理如同 “慢动作” 一样分解后,把各个处理(启动、执行、准备返回值、使用返回值)分配给各个线程——这就是我们使用多线程这个工具进行的操作。

9.4.4 变种——不让主线程久等的Future角色

  1. 如果 FutureData 的 getContent 方法被调用时,RealData 的实例还没有创建完成,则使用Guarded Suspension 模式来 “等待创建完成”。正因为有了 “等待创建完成”,主线程从FutureData的 getContent 方法返回时才可以获取到必要的信息。
  2. 不过,getContent方法的实现也可能是异步的。虽说是异步,但并不像在 getContent 方法中启动新线程那么麻烦。这里可以使用 Balking 模式来实现 “如果还未创建完成就暂时返回”。这样一来,如果RealData的实例没有创建完成,程序的控制权就会暂时返回主线程,然后稍微执行一点其他操作后再去调用getContent

9.4.5 变种——会发生变化的Future角色

  1. 通常情况下,“返回值” 仅会被设置到 Future 角色中一次。也就是说,Future 角色是 “状态只会改变一次的变量(latch)”。但是,有时也可能会有给Future角色设置 “当前返回值” 的需求。这时,我们可以考虑反复设置 “返回值”
  2. 例如,在通过网络获取图像数据时,我们希望在最开始先获取图像的长和宽,接着获取模糊图像数据,最后获取清晰图像数据。此时,Future角色就可能会派上用场。

9.4.6 回调与Future模式

  1. 如果想要等待处理完成后获取返回值,还可以考虑采用回调处理方式。
  2. 所谓回调,是指当处理终止后,由 Host 角色启动的线程去调用 Client 角色的方法这一方式。不过,这种情况下,Client角色中就会有与多线程相关的处理。具体而言,必须在Client角色中编写用于安全地传递返回值的代码。

9.6 java.util.concurrent 包与Future模式

  1. java.util.concurrent.Callable接口将 “返回值的某种处理的调用” 抽象化了。
    Callable接口声明了call方法。call 方法与 Runnable 接口的 run 方法相似,不同的是 call 方法有返回值Callable<string>这个表达式表示 “Callable接口的 call 方法的返回值的类型是string”。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值