zoukankan      html  css  js  c++  java
  • Future模式

      Future模式是多线程开发中非常常见的一种设计模式,它的核心思想是异步调用。当我们需要调用一个方法时,如果这个方法执行得很慢,那么我们就要进行等待;但是有时候,我们可能并不急着要结果。因此,我们可以让被调用者立即返回,让它在后台慢慢处理这个请求。对于调用者来说,则可以先处理一些其他任务,在真正需要数据的场合再去尝试获得需要的数据。
      Future模式有点类似于网上买东西,如果我们网上下单购买了一台手机,当我们支付完成后,手机并没有立即送到家里,但是在电脑上回产生一个订单。这个订单就是将来发货或者领取手机的凭证,这个凭证就是future模式给出的一个契约。在支付结束后,大家不会傻傻的等待手机的到来,而是各忙各的。而这张订单就成为了商家配货、发货的驱动力。当然这一切都不用关心。你要做的,只是快递上门,开门拿货而已。
      对于Future模式来说,虽然它无法立即给出你要的数据,但是,它会立即返回给你一个契约,将来,你可以凭着这个契约去重新获取你需要的信息。
      通过传统的同步方法调用一段比较耗时的程序,调用过程应该是这样的:客户端发出请求,这个请求要相当一段时间才能返回。客户端一直等待,直到数据返回,随后,再进行其他的任务处理。
      使用Future模式替换原来的实现方式,可以改进其调用过程。如下图所示:

      上面的模型展示了一个广义的Future对象,从Data_Future对象可以看出,虽然call本身需要很长一段时间处理程序。但是,服务程序不等数据处理完便立即返回客户端一个伪造的数据(相当于商品的订单,而不是商品本身),实现了Future模式的客户端在拿到这个结果后,并不急于对其进行处理,而是调用了其他业务逻辑,充分利用了等待时间,这就是Future模式的核心所在。在完成了其他业务逻辑处理后,最后再使用返回比较慢的Future数据,这样,在整个调用的过程中,就不存在无谓的等待,充分利用了时间,从而提高了系统的响应速度。

    Future模式的主要角色

    参与者 作用
    Main 系统启动,调用Client发出请求
    Client 返回Data对象,立即返回FutureData,并开启ClientThread线程装配RealData
    Data 返回数据的接口
    FutureData Future数据,构造很快,但是是一个虚拟的数据,需要装配RealData
    RealData 真实数据,其构造是比较慢的

    Future模式的简单实现

    1 public interface Data {
    2     public String getResult();
    3 }

      核心接口Data,这就是客户端希望获取到的数据。

     1 public class RealData implements Data {
     2 
     3     protected final String result;
     4 
     5     public RealData(String para) throws InterruptedException {
     6         //RealData的构造很慢,需要用户等很久,这里使用sleep模拟
     7         StringBuffer sb = new StringBuffer();
     8         for (int i = 0;i < 10;i++){
     9             sb.append(para);
    10             Thread.sleep(100);//sleep模拟很慢的动作
    11         }
    12         result = sb.toString();
    13     }
    14     @Override
    15     public String getResult() {
    16         return result;
    17     }
    18 }

      RealData是最终需要使用到的数据模型。它的构造很慢,在这个例子中,我们使用了使线程休眠来模拟了这个过程。

     1 public class FutureData implements Data {
     2 
     3     protected RealData realData = null;
     4     protected boolean isReady = false;
     5 
     6     public synchronized void setRealData(RealData realData){
     7         if (isReady){
     8             System.out.println("-----------");
     9             return;
    10         }
    11         System.out.println("........................");
    12         this.realData = realData;
    13         System.out.println("realData: " + realData);
    14         isReady = true;
    15         notifyAll();//realData已经被注入,通知getResult()
    16     }
    17 
    18     @Override
    19     public synchronized String getResult() {
    20         while (!isReady){
    21             try {
    22                 System.out.println("Waiting");
    23                 wait();//等待,直到realData被注入
    24             } catch (InterruptedException e) {
    25                 e.printStackTrace();
    26             }
    27         }
    28         return realData.result;
    29     }
    30 }

      FutureData实现了一个快速返回的RealData的包装。它只是一个包装,或者说是RealData的虚拟实现。因此,它可以很快被构造并返回。当使用FutureData的getResult()方法时,如果实际的数据没有准备好,那么程序就会阻塞,等待RealData准备好并注入FutureData中,才最终返回数据。FutureData是Future模式的关键,它实际上是真实数据的代理,封装了获取RealData的等待过程。

     1 public class Client {
     2     public Data request(final String queryStr){
     3         final FutureData futureData = new FutureData();
     4         new Thread() {
     5             @Override
     6             public void run() {
     7                 try {//realData构造很慢,所以在单独的线程中执行
     8                     RealData realData = new RealData(queryStr);
     9                     System.out.println("调用setRealData");
    10                     futureData.setRealData(realData);
    11                 } catch (InterruptedException e) {
    12                     e.printStackTrace();
    13                 }
    14             }
    15         }.start();
    16         System.out.println("futureData : " + futureData);
    17         return futureData;//futureData对象会被立即返回
    18     }
    19 }

      Client主要实现了获取FutureData,并开启构造RealData的线程。并在接收请求后,很快的返回FutureData。注意,他不会等待真正的数据构造完毕后再返回,而是立即返回FutureData,即使这个时候FutureData内并没有真实数据。

     1 public class Main {
     2     //测试
     3     public static void main(String[] args){
     4         Client client = new Client();
     5         //这里立即返回的是futureData不是RealData
     6         Data data = client.request("name");
     7         System.out.println("请求完毕!");
     8         try {
     9             Thread.sleep(1000);//这里使用sleep代替其他任务处理,在处理这些任务的过程中,RealData被创建,充分利用了时间
    10         } catch (InterruptedException e) {
    11             e.printStackTrace();
    12         }
    13         System.out.println("数据 = " + data.getResult());
    14     }
    15 }

      Main主要负责调用Client发起请求,并消费返回的数据。

    输出结果:

    futureData : CurrentJava.future.FutureData@4554617c
    请求完毕!
    Waiting
    调用setRealData
    ........................
    realData: CurrentJava.future.RealData@b20568f
    数据 = namenamenamenamenamenamenamenamenamename

      从输出结果可以看出:客户端发送请求,立即返回了对象FutureData,返回请求完毕,Client的内部线程启动构造真实的数据,由于此时RealData并没有构造完毕,所以构造线程wait处于等待状态,当RealData构造完毕后,唤醒了等待线程,拿到真实数据,并返回。这就是Future模式的调用过程。

    JDK中的Future模式

      由于Future模式是非常常用的多线程设计模式,因此在JDK中内置了Future模式的实现。这些类在java.util.concurrent包中,其中最重要的是FutureTask类,它实现了Runnable接口,可以作为单独线程运行,在它的run()方法中,通过Sync内部类调用Callable接口,并维护Callable接口的返回对象,使用FutureTask.get()方法时,将返回Callable接口的返回对象。

      将上述例子,用JDK自带的Future模式来实现:

      首先,Data接口和FutureData就不需要了,JDK已经帮我们实现了;

      其次,RealData修改为:

     1 public class RealData implements Callable<String> {
     2 
     3     private String para;
     4     public RealData(String para){
     5         this.para = para;
     6     }
     7     @Override
     8     public String call() throws Exception {
     9         StringBuffer sb = new StringBuffer();
    10         for (int i = 0; i < 10;i++){
    11             sb.append(para);
    12             Thread.sleep(100);
    13         }
    14         return sb.toString();
    15     }
    16 }

      上述的代码实现了Callable接口,它的call()方法会构造我们需要的真实数据并返回,我们用sleep来模拟其缓慢的过程。

     1 public class FutureMain {
     2 
     3     public static void main(String[] args) throws InterruptedException, ExecutionException {
     4         //构造FutureTask
     5         FutureTask<String> futureTask = new FutureTask<String>(new RealData("a"));
     6         ExecutorService es = Executors.newFixedThreadPool(1);
     7         es.execute(futureTask);//执行FutureTask,开启线程执行RealData的call()
     8         System.out.println(futureTask.isDone());
     9         System.out.println("请求完成!");
    10         Thread.sleep(2000);//sleep代替其他业务逻辑处理
    11         System.out.println("数据:" + futureTask.get());
    12         es.shutdown();
    13     }
    14 }

      上述代码就是Future模式的典型。在第5行,构造了FutureTask对象实例,表示这个任务是有返回值的;同时构造FutureTask时,使用了Callable接口,告诉FutureTask我们需要的真实数据应该如何产生。接着在第7行,将FutureTask提交给线程池,提交完成后,立即回有FutureData返回,然后我们做一些额外的事情(这里使用sleep代替),最后再我们需要的时候通过FutureTask.get()得到实际真实的数据。

    除了基本的功能外,JDK还为Future接口提供了一些简单的控制功能:

      boolean isCancelled():是否已经取消;

      boolean isDone():是否已经完成;

      boolean cancel(boolean mayInterruptIfRunning):取消任务;

      V get() throws InterruptedException, ExecutionException :取得返回的对象;

      V get(long timeout, TimeUnit unit):在规定时间内取得返回的对象。

    参考:《Java高并发程序设计》 葛一鸣 郭超 编著:

    作者:Joe
    努力了的才叫梦想,不努力的就是空想,努力并且坚持下去,毕竟这是我相信的力量
  • 相关阅读:
    Entity Framework 教程
    C# yield
    表达式树系列文章汇总
    C#中静态与非静态方法比较
    谈谈对Spring IOC的理解
    工厂方法模式与IoC/DI
    通过配置的方式Autofac 《第三篇》
    Autofac 组件、服务、自动装配 《第二篇》
    Autofac 解释第一个例子 《第一篇》
    SQL Server索引调优系列
  • 原文地址:https://www.cnblogs.com/Joe-Go/p/9815143.html
Copyright © 2011-2022 走看看