多线程异步调用之Future模式

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了多线程异步调用之Future模式相关的知识,希望对你有一定的参考价值。

参考技术A

当我们调用一个函数的时候,如果这个函数的执行过程是很耗时的,我们就必须要等待,但是我们有时候并不急着要这个函数返回的结果。因此,我们可以让被调者立即返回,让他在后台慢慢的处理这个请求。对于调用者来说,则可以先处理一些其他事情,在真正需要数据的时候再去尝试获得需要的数据(这个真正需要数据的位置也就是上文提到的阻塞点)。这也是Future模式的核心思想:异步调用。

到了这里,你可能会想CountDownLatch不是也可以实现类似的功能的吗?也是可以让耗时的任务通过子线程的方式去执行,然后设置一个阻塞点等待返回的结果,情况貌似是这样的!但有时发现CountDownLatch只知道子线程的完成情况是不够的,如果在子线程完成后获取其计算的结果,那CountDownLatch就有些捉襟见衬了,所以JDK提供的Future类,不仅可以在子线程完成后收集其结果,还可以设定子线程的超时时间,避免主任务一直等待。

看到这里,似乎恍然大悟了!CountDownLatch无法很好的洞察子线程执行的结果,使用Future就可以完成这一操作,那么Future何方神圣!下边我们就细细聊一 下。

虽然,Future模式不会立即返回你需要的数据,但是,他会返回一个契约 ,以后在使用到数据的时候就可以通过这个契约获取到需要的数据。

上图显示的是一个串行程序调用的流程,可以看出当有一个程序执行的时候比较耗时的时候,其他程序必须等待该耗时操作的结束,这样的话客户端就必须一直等待,知道返回数据才执行其他的任务处理。

上图展示的是Future模式流程图,在广义的Future模式中,虽然获取数据是一个耗时的操作,但是服务程序不等数据完成就立即返回客户端一个伪造的数据(就是上述说的“契约”),实现了Future模式的客户端并不急于对其进行处理,而是先去处理其他业务,充分利用了等待的时间,这也是Future模式的核心所在,在完成了其他数据无关的任务之后,最后在使用返回比较慢的Future数据。这样在整个调用的过程中就不会出现长时间的等待,充分利用时间,从而提高系统效率。

1、Future主要角色

2、Future的核心结构图如下:

上述的流程就是说:Data为核心接口,这是客户端希望获取的数据,在Future模式中,这个Data接口有两个重要的实现,分别是:RealData和FutureData。RealData就是真实的数据,FutureData他是用来提取RealData真是数据的接口实现,用于立即返回得到的,他实际上是真实数据RealData的代理,封装了获取RealData的等待过程。

说了这些理论的东西,倒不如直接看代码来的直接些,请看代码!

主要包含以下5个类,对应着Future模式的主要角色:

1、Data接口

2、FutureData代码

3、RealData代码

4、Client代码

5、Main

6、执行结果:

上述实现了一个简单的Future模式的实现,因为这是一个很常用的模式,在JDK中也给我们提供了对应的方法和接口,先看一下实例:

这里的RealData 实现了Callable接口,重写了call方法,在call方法里边实现了构造真实数据耗时的操作。

执行结果:

上述代码,通过:FutureTask<String> futureTask = new FutureTask<>(new RealData("Hello")); 这一行构造了一个futureTask 对象,表示这个任务是有返回值的,返回类型为String,下边看一下FutureTask的类图关系:

FutureTask实现了RunnableFuture接口,RunnableFuture接口继承了Future和Runnable接口。因为RunnableFuture实现了Runnable接口,因此FutureTask可以提交给Executor进行执行,FutureTask有两个构造方法,如下:

构造方法1,参数为Callable:

构造方法2,参数为Runnable:

上述的第二个构造方法,传入的是Runnable接口的话,会通过Executors.callable()方法转化为Callable,适配过程如下:

这里为什么要将Runnable转化为Callable哪?首先看一下两者之间的区别:

最关键的是第二点,就是Callable具有返回值,而Runnable没有返回值。Callable提供了检查计算是否完成的方法,以等待计算的完成,并获取计算的结果。

计算完成后只能使用 get 方法来获取结果,如果线程没有执行完,Future.get()方法可能会阻塞当前线程的执行;如果线程出现异常,Future.get()会throws InterruptedException或者ExecutionException;如果线程已经取消,会抛出CancellationException。取消由cancel 方法来执行。isDone确定任务是正常完成还是被取消了。

多线程之Future模式

  对于多线程,当A线程需要获得B线程的处理结果,而B线程处理业务需要很长时间,这时候A线程如果一直等待B线程的处理结果,线程A才能继续往下执行代码,这种方式在效率不是很好。所以,这种场景可以使用多线程的Future模式。

  Future模式,就是当A线程需要B线程的处理结果,启动B线程,然后A线程继续往下走,而不必一直等待B线程的结果,当A线程需要用到B线程的结果时候再去获取结果,如果B线程还没处理好业务逻辑,则A线程一直处于阻塞状态。直到B线程处理完成。下面总结一下Future模式的实现方式。

  首先客户端向服务器请求,但是这个资源的创建是非常耗时的,这种情况下,首先返回Client一个FutureSubject,以满足客户端的需求,同时,Future会通过另外一个Thread 去构造一个真正的资源,资源准备完毕之后,在给future一个通知。如果客户端急于获取这个真正的资源,那么就会阻塞客户端的其他所有线程,等待资源准备完毕。

  公共数据接口,FutureData和RealData都要实现

public interface Data {

    public abstract String getConent();
}
public class FutureData implements Data{

    private RealData realData;
    
    private boolean isReady=false;
    
  //进行真实数据的装载
public synchronized void setRealData(RealData realData){ if(isReady) return; isReady=true; this.realData=realData; notify(); } //如果业务还没处理完成,则进入阻塞状态 public synchronized String getConent() { while(!isReady){ try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } }
    //业务逻辑处理完成,直接返回真实结果
return this.realData.getConent(); } }
public class RealData implements Data{

    private String result;
    //真正处理业务逻辑的方法
    public RealData(String queryStr){
        System.out.println("这是一个很耗时的请求...");
        try {
            Thread.sleep(5000);
        } 
        catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("操作完毕,获取结果");
        result="查询结果";
    }
    
    public String getConent() {

        return result;
    }

}
public class FutureClient {
    //处理客户端的请求
    public Data request(final String queryStr){
    
final FutureData futureDate =new FutureData();
     //开启一个新的线程去处理复杂的业务逻辑
new Thread(new Runnable() { public void run() { RealData realData=new RealData(queryStr);
         //处理完成对数据进行装载 futureDate.setRealData(realData); } }).start();
     //返回给客户端
return futureDate; } }
public class TestFuture {

    public static void main(String[]args){
        FutureClient fc=new FutureClient();
        Data data=fc.request("查询条件..");
        System.out.println("请求发送成功!");
        System.out.println("做其他的事情...");
        String result = data.getConent();
        System.out.println(result);
        
    }
}

  在JDK1.5 Concurrent 中,提供了这种Callable和Future。Callable产生结果,Future拿到结果。 代码如下:

public class Task implements Callable<String> {

    public String call() throws Exception {

        Thread.sleep(5000);
        
        return "Hello Future";
    }
}

  首先处理业务逻辑的类必须实现Callable接口,他与runable接口的区别是,runable接口重写的Run方法没有返回值,而Callable接口可以定义返回值。

public class FutureDemo {

    public static void main(String[]args) throws InterruptedException, ExecutionException{
        
         System.out.println("已经提交资源申请");
         
         ExecutorService executor=Executors.newCachedThreadPool();
         
         Task task =new Task();
         
         Future<String> future=executor.submit(task);
         
         System.out.println("继续做其他事情...");
         
         if(!future.isDone()){
             System.out.println("任务还没处理完成.."); 
         }
         System.out.println("获取返回结果:"+future.get());
         
         if(future.isDone()){
             System.out.println("任务处理完成"); 
         }
         executor.shutdown();
    }
}

  使用线程池 ExecutorService executor=Executors.newCachedThreadPool(),调用submit方法开启线程,submit方法与executor方法的区别是,submit可以传入实现Callable或者runable接口类型的参数,而executor只可以传入实现runable接口类型的参数。

以上是关于多线程异步调用之Future模式的主要内容,如果未能解决你的问题,请参考以下文章

多线程设计模式 - Future模式

多线程(10) — Future模式

学习多线程中的 Future 模式一篇文章就够了 !!!

多线程(二十二异步执行-Futrue模式)

并发编程之Callable异步,Future模式

多线程之Future模式