返回介绍

5.5 Future模式

发布于 2024-08-21 22:20:21 字数 7030 浏览 0 评论 0 收藏 0

Future模式是多线程开发中非常常见的一种设计模式,它的核心思想是异步调用。当我们需要调用一个函数方法时,如果这个函数执行很慢,那么我们就要进行等待。但有时候,我们可能并不急着要结果。因此,我们可以让被调者立即返回,让它在后台慢慢处理这个请求。对于调用者来说,则可以先处理一些其他任务,在真正需要数据的场合再去尝试获得需要的数据。

Future模式有点类似在网上买东西。如果我们在网上下单买了一个手机,当我们支付完成后,手机并没有办法立即送到家里,但是在电脑上会立即产生一个订单。这个订单就是将来发货或者领取手机的重要凭证,这个凭证也就是Future模式中会给出的一个契约。在支付活动结束后,大家不会傻傻地等着手机到来,而是可以各忙各的。而这张订单就成为了商家配货、发货的驱动力。当然,这一切你并不用关心。你要做的,只是在快递上门时,开一下门,拿一下货而已。

对于Future模式来说,虽然它无法立即给出你需要的数据。但是,它会返回给你一个契约,将来,你可以凭借着这个契约去重新获取你需要的信息。

如图5.6所示,显示了通过传统的同步方法,调用一段比较耗时的程序。客户端发出call请求,这个请求需要相当长一段时间才能返回。客户端一直等待,直到数据返回,随后,再进行其他任务的处理。

图5-6 传统串行程序调用流程

使用Future模式替换原来的实现方式,可以改进其调用过程,如图5.7所示。

图5-7 Future模式流程图

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

5.5.1 Future模式的主要角色

为了让大家能够更清晰地认识Future模式的基本结构。在这里,我给出一个非常简单的Future模式的实现,它的主要参与者如表5.2所示。

表5.2 Future模式的主要参与者

它的核心结构如图5.8所示。

图5.8 Future模式结构图

5.5.2 Future模式的简单实现

在这个实现中,有一个核心接口Data,这就是客户端希望获取的数据。在Future模式中,这个Data接口有两个重要的实现,分别是RealData,也就是真实数据,这就是我们最终需要获得的,有价值的信息。另外一个就是FutureData,它就是用来提取RealData的一个“订单”。因此FutureData是可以立即返回得到的。

下面是Data接口:

public interface Data {
  public String getResult ();
}

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

注意:FutureData是Future模式的关键。它实际上是真实数据RealData的代理,封装了获取RealData的等待过程。

public class FutureData implements Data {
  protected RealData realdata = null;      //FutureData是RealData的包装
  protected boolean isReady = false;
  public synchronized void setRealData(RealData realdata) {
    if (isReady) {
      return;
    }
    this.realdata = realdata;
    isReady = true;
    notifyAll();                 //RealData已经被注入,通知getResult()
  }
  public synchronized String getResult() {     //会等待RealData构造完成
    while (!isReady) {
      try {
        wait();              //一直等待,知道RealData被注入
      } catch (InterruptedException e) {
      }
    }
    return realdata.result;          //由RealData实现
  }
}

RealData是最终需要使用的数据模型。它的构造很慢。在这里,使用sleep()函数模拟这个过程,简单地模拟一个字符串的构造。

public class RealData implements Data {
  protected final String result;
  public RealData(String para) {
    //RealData的构造可能很慢,需要用户等待很久,这里使用sleep模拟
    StringBuffer sb=new StringBuffer();
    for (int i = 0; i < 10; i++) {
      sb.append(para);
      try {
  //这里使用sleep,代替一个很慢的操作过程
        Thread.sleep(100);
      } catch (InterruptedException e) {
      }
    }
    result =sb.toString();
  }
  public String getResult() {
    return result;
  }
}

接下来就是我们的客户端程序,Client主要实现了获取FutureData,并开启构造RealData的线程。并在接受请求后,很快的返回FutureData。注意,它不会等待数据真的构造完毕再返回,而是立即返回FutureData,即使这个时候FutureData内并没有真实数据。

public class Client {
  public Data request(final String queryStr) {
    final FutureData future = new FutureData();
    new Thread() {
      public void run() {         // RealData的构建很慢,
                        //所以在单独的线程中进行
        RealData realdata = new RealData(queryStr);
        future.setRealData(realdata);
      }
    }.start();
    return future;              // FutureData会被立即返回
  }
}

最后,就是我们的主函数Main,它主要负责调用Client发起请求,并消费返回的数据。

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

5.5.3 JDK中的Future模式

Future模式是如此常用,因此JDK内部已经为我们准备好了一套完整的实现。显然,这个实现要比我们前面提出的方案复杂得多。在这里,我们将简单向大家介绍一下它的使用方式。

首先,让我们看一下Future模式的基本结构,如图5.9所示。其中Future接口就类似于前文描述的订单或者说是契约。通过它,你可以得到真实的数据。RunnableFuture继承了Future和Runnable两个接口,其中run()方法用于构造真实的数据。它有一个具体的实现FutureTask类。FutureTask有一个内部类Sync,一些实质性的工作,会委托Sync类实现。而Sync类最终会调用Callable接口,完成实际数据的组装工作。

图5.9 JDK内置的Future模式

Callable接口只有一个方法call(),它会返回需要构造的实际数据。这个Callable接口也是这个Future框架和应用程序之间的重要接口。如果我们要实现自己的业务系统,通常需要实现自己的Callable对象。此外,FutureTask类也与应用密切相关,通常,我们会使用Callable实例构造一个FutureTask实例,并将它提交给线程池。

下面我们将展示这个内置的Future模式的使用:

01 public class RealData implements Callable<String> {
02   private String para;
03   public RealData(String para){
04     this.para=para;
05   }
06   @Override
07   public String call() throws Exception {
08
09     StringBuffer sb=new StringBuffer();
10     for (int i = 0; i < 10; i++) {
11       sb.append(para);
12       try {
13         Thread.sleep(100);
14       } catch (InterruptedException e) {
15       }
16     }
17     return sb.toString();
18   }
19 }

上述代码实现了Callable接口,它的call()方法会构造我们需要的真实数据并返回。当然这个过程可能是缓慢的,这里使用Thread.sleep()模拟它:

01 public class FutureMain {
02   public static void main(String[] args) throws InterruptedException, ExecutionException {
03     //构造FutureTask
04     FutureTask<String> future = new FutureTask<String>(new RealData("a"));
05     ExecutorService executor = Executors.newFixedThreadPool(1);
06     //执行FutureTask,相当于上例中的 client.request("a") 发送请求
07     //在这里开启线程进行RealData的call()执行
08     executor.submit(future);
09
10     System.out.println("请求完毕");
11     try {
12     //这里依然可以做额外的数据操作,这里使用sleep代替其他业务逻辑的处理
13       Thread.sleep(2000);
14     } catch (InterruptedException e) {
15     }
16     //相当于5.5.2节中得data.getResult (),取得call()方法的返回值
17     //如果此时call()方法没有执行完成,则依然会等待
18     System.out.println("数据 = " + future.get());
19   }
20 }

上述代码就是使用Future模式的典型。第4行,构造了FutureTask对象实例,表示这个任务是有返回值的。构造FutureTask时,使用Callable接口,告诉FutureTask我们需要的数据应该如何产生。接着再第8行,将FutureTask提交给线程池。显然,作为一个简单的任务提交,这里必然是立即返回的,因此程序不会阻塞。接下来,我们不用关心数据是如何产生的。可以去做一些额外的事情,然后在需要的时候可以通过Future.get()(第18行)得到实际的数据。

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

boolean cancel(boolean mayInterruptIfRunning);       //取消任务
boolean isCancelled();                   //是否已经取消
boolean isDone();                      //是否已完成
V get() throws InterruptedException, ExecutionException;   //取得返回对象
V get(long timeout, TimeUnit unit)             //取得返回对象,可以设置超时时间

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文