多线程异步调用之Future模式

一、什么是异步调用

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

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

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

二、Future模式

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

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

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

1、Future主要角色

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

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

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

三、Future模式的简单实现

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

1、Data接口

/**
 * 返回数据的接口
 */
public interface Data {

    String getResult();
}

2、FutureData代码

/**
 * Future数据,构造很快,但是是一个虚拟的数据,需要装配RealData
 */
public class FutureData implements Data {

    private RealData realData = null;
    private boolean isReady = false;

    private ReentrantLock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    @Override
    public String getResult() {
        while (!isReady) {
            try {
                lock.lock();
                condition.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
        return realData.getResult();
    }

    public void setRealData(RealData realData) {
        lock.lock();
        if (isReady) {
            return;
        }
        this.realData = realData;
        isReady = true;
        condition.signal();
        lock.unlock();
    }
}

3、RealData代码

public class RealData implements Data {

    private String result;

    public RealData(String param) {
        StringBuffer sb = new StringBuffer();
        sb.append(param);
        try {
            //模拟构造真实数据的耗时操作
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        result = sb.toString();
    }

    @Override
    public String getResult() {
        return result;
    }
}

4、Client代码

public class Client {

    public Data request(String param) {
        //立即返回FutureData
        FutureData futureData = new FutureData();
        //开启ClientThread线程装配RealData
        new Thread(() -> {
            {
                //装配RealData
                RealData realData = new RealData(param);
                futureData.setRealData(realData);
            }
        }).start();
        return futureData;
    }
}

5、Main

/**
 * 系统启动,调用Client发出请求
 */
public class Main {

    public static void main(String[] args) {
        Client client = new Client();
        Data data = client.request("Hello Future!");
        System.out.println("请求完毕!");

        try {
            //模拟处理其他业务
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("真实数据:" + data.getResult());
    }
}

6、执行结果:

四、JDK中的Future模式实现

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

public class RealData implements Callable<String> {

    private String result;

    public RealData(String result) {
        this.result = result;
    }

    @Override
    public String call() throws Exception {
        StringBuffer sb = new StringBuffer();
        sb.append(result);
        //模拟耗时的构造数据过程
        Thread.sleep(5000);
        return sb.toString();
    }
}

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

public class FutureMain {

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        FutureTask<String> futureTask = new FutureTask<>(new RealData("Hello"));

        ExecutorService executorService = Executors.newFixedThreadPool(1);
        executorService.execute(futureTask);

        System.out.println("请求完毕!");

        try {
            Thread.sleep(2000);
            System.out.println("这里经过了一个2秒的操作!");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("真实数据:" + futureTask.get());
        executorService.shutdown();
    }
}

执行结果:

上述代码,通过: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哪?首先看一下两者之间的区别:

(1) Callable规定的方法是call(),Runnable规定的方法是run();

(2) Callable的任务执行后可返回值,而Runnable的任务是不能返回值得;

(3) call()方法可以抛出异常,run()方法不可以;

(4) 运行Callable任务可以拿到一个Future对象,Future 表示异步计算的结果。

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

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

一旦计算完成,就不能再取消计算。如果为了可取消性而使用 Future 但又不提供可用的结果,则可以声明Future<?> 形式类型、并返回 null 作为底层任务的结果。

原文地址:https://blog.51cto.com/14257804/2394035

时间: 2024-10-11 09:36:46

多线程异步调用之Future模式的相关文章

HttpContext在多线程异步调用中的使用方案

1.在线程调用中,有时候会碰到操作文件之类的功能.对于开发人员来说,他们并不知道网站会被部署在服务器的那个角落里面,因此根本无法确定真实的物理路径(当然可以使用配置文件来配置物理路径),他们唯一知道的就是文件在项目中的相对路径,为了定位文件路径,一般都会调用HttpContext.Current.Request.MapPath或者HttpContext.Current.Server.MapPath,但是在多线程调用中,HttpContext肯定为null,这时候还调用MapPath结果就是报错.

同步调用,异步回调和 Future 模式

目标 通过与方法的同步调用,异步回调比较,理解 Future 模式 三者的不同 让我们先来明确一下同步与异步的不同.我们这里所说的同步和异步,仅局限在方法的同步调用和异步回调中.即,同步指的是调用一个方法,调用方要等待该方法所执行的任务完全执行完毕,然后控制权回到调用方:异步指的是调用一个方法,调用方不等该方法执行的任务完毕就返回,当任务执行完毕时会自动执行调用方传入的一块代码. 同步调用 void runTask { doTask1() doTask2() } 同步调用,执行完 doTask1

多线程(10) — Future模式

Future模式是多线程开发中常用常见的一种设计模式,它的核心思想是异步调用.在调用一个函数方法时候,如果函数执行很慢,我们就要进行等待,但这时我们可能不着急要结果,因此我们可以让被调者立即返回,让它在后台慢慢处理这个请求,对于调用者来说可以先处理一些其他事物,在真正需要数据的场合再去尝试获得需要的数据.对于Future模式来说,虽然它无法立即给出你需要的数据,但是它们返回一个契约给你,将来你可以凭借这个契约去重新获取你需要的信息.主要的角色有: Main:系统启动,调用Client发出请求.

Junit调试解决本地多线程异步调用

Thread t = new Thread(new Runnable(){ @Override public void run(){ // run方法具体重写 try { handledataByAsync(inMap, busiMap); } catch (Exception e) { e.printStackTrace(); } }}); t.start(); // 本地调试打开 join方法 // t.join(); 原文地址:https://www.cnblogs.com/zhangli

Java多线程编程中Future模式的详解&lt;转&gt;

Java多线程编程中,常用的多线程设计模式包括:Future模式.Master-Worker模式.Guarded Suspeionsion模式.不变模式和生产者-消费者模式等.这篇文章主要讲述Future模式,关于其他多线程设计模式的地址如下:关于其他多线程设计模式的地址如下:关于Master-Worker模式的详解: Java多线程编程中Master-Worker模式的详解关于Guarded Suspeionsion模式的详解: Java多线程编程中Guarded Suspeionsion模式

java 异步调用与多线程

异步与多线程的区别 一.异步和多线程有什么区别?其实,异步是目的,而多 线程是实现这个目的的方法.异步是说,A发起一个操作后(一般都是比较耗时的操作,如果不耗时的操作 就没有必要异步了),可以继续自顾自的处理它自己的事儿,不用干等着这个耗时操作返回..Net中的这种异步编程模型,就简化了多线程编程,我们甚至都不 用去关心Thread类,就可以做一个异步操作出来. 二.随着拥有多个硬线程CPU(超线程.双核)的普及,多线程和异步操作等并发程序设计方法也受到了更多的关注和讨论.本文主要是想探讨一下如

并行设计模式(一)-- Future模式

Java多线程编程中,常用的多线程设计模式包括:Future模式.Master-Worker模式.Guarded Suspeionsion模式.不变模式和生产者-消费者模式等.这篇文章主要讲述Future模式,关于其他多线程设计模式的地址如下: 关于Master-Worker模式的详解:并行设计模式(二)-- Master-Worker模式 关于Guarded Suspeionsion模式的详解:并行设计模式(三)-- Guarded Suspeionsion模式 关于不变模式的详解:并行设计模

java Future模式

Java多线程编程中,常用的多线程设计模式包括:Future模式.Master-Worker模式.Guarded Suspeionsion模式.不变模式和生产者-消费者模式等.这篇文章主要讲述Future模式,关于其他多线程设计模式的地址如下:关于其他多线程设计模式的地址如下:关于Master-Worker模式的详解: Java多线程编程中Master-Worker模式的详解关于Guarded Suspeionsion模式的详解: Java多线程编程中Guarded Suspeionsion模式

cglib实现全配置的异步调用

参考cglib资料:http://www.tuicool.com/articles/IVfANr 实现逻辑为,对service类用cglib包装,让其调用方法改成异步调用,Future.线程池 对service方法的返回对象也用cglib包装,使其带有LazyLoad的功能 主要点:对cglib需要处理的类信息缓存,提高效率,通过lazyload,让用户不需要修改自己的代码,通过配置的方式就能进行异步化调用方法