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高并发程序设计》 葛一鸣 郭超 编著:

 

posted on 2018-10-19 10:53  AoTuDeMan  阅读(208)  评论(0编辑  收藏  举报

导航