Java并行程序设计模式——Future模式

标签: Future模式

问题引出

现在我们想要炒一道菜,但是我们没有厨具和菜,现在我们从网上订购了一套厨具,但在厨具送来的期间,我们不必一直等到厨具到来,而是可以先去买菜,然后厨具到了之后直接开始炒菜

这就是Future模式,在程序设计中,当某一段程序提交了一个请求,期望得到一个答复。但非常不幸的是,服务程序对这个请求的处理可能很慢,比如这个请求可能是通过互联网、HTTP或者Web Service等并不太高效的方式调用的。在传统的单线程模式下,调用函数是同步的,也就是说他必须等到服务程序返回结果后,才能进行其他处理而在Future模式下,调用方式改为异步,而原先等待返回的时间段,在主调用函数中,则可用于处理其他事务

传统模式与Future模式流程对比

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

Future模式的代码实现

在这里插入图片描述

参与者 作用
Main 系统启动,调用Client发送请求,并使用返回数据
Client 返回Data对象,立即返回FutureData,并开启ClientThread线程装配RealData
Data 返回数据的接口
FutureData Future数据,构造很快,但是是一个虚拟数据,需要装配RealData
RealData 真实数据,构造比较缓慢
public class Main {
    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(1000);
            System.out.println("其他操作");
        } catch (InterruptedException e) {

        }
        // 使用真实数据
        System.out.println("真实数据:" + data.getResult());
    }
}
public class Client {
    public Data request(final String queryStr) {
        final FutureData futureData = new FutureData();
        new Thread() {
            @Override
            public void run() {
                RealData realData = new RealData(queryStr);
                futureData.setRealData(realData);
            }
        }.start();
        return futureData;
    }
}
public interface Data {
    public String getResult();
}
// FutureData是Future模式的关键,它实际上是真实数据RealData的代理,封装了获取RealData的等待过程
public class FutureData implements Data {

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

    public synchronized void setRealData(RealData realData) {
        if (isReady) {
            return;
        }
        this.realData = realData;
        this.isReady = true;
        notifyAll();        // RealData已经被注入,通知getResult()
    }

    @Override
    public synchronized String getResult() {    // 会等待RealData构造完成
        while (!isReady) {
            try {
                wait();             // 一直等待,直到RealData注入
            } catch (InterruptedException e) {

            }
        }
        return realData.result;        // 由RealData实现
    }
}
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(1000);
            } catch (InterruptedException e) {

            }
        }
        result = sb.toString();
    }

    @Override
    public String getResult() {
        return result;
    }
}
// 运行结果
请求完毕
// 等待一秒后
其他操作
//(等待10秒)
真实数据:name name name name name name name name name name 

扩展

由于Future模式的常用性,以至于JDK的并发包中就已经内置了一种Future模式的实现。JDK中的实现是相当复杂的,并提供了更为丰富的线程控制功能,但其中的基本用意和核心概念是完全一致的

在这里插入图片描述使用JDK内置的Future模式

public class RealData implements Callable<String> {
    private String para;

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

    @Override
    public String call() throws Exception {
    	// 这里是真实的业务逻辑,其执行可能会很慢
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < 10; i++) {
            sb.append(para + " ");
            try {
                // 这里使用sleep模拟,代替一个很慢的操作
                Thread.sleep(1000);
            } catch (InterruptedException e) {

            }
        }
        return sb.toString();
    }
}
// 由于使用了JDK内置的框架,Data接口,FutureData等对象就不再需要了
// 直接通过RealData构造FutureTask,并将其作为单独的线程运行。在提交结果后,执行其他业务逻辑,最后通过FutureTask.get()方法得到RealData的执行结果
public class Main {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        // 构造FutureTask
        FutureTask<String> futureTask = new FutureTask<String>(new RealData("a"));
        ExecutorService executor = Executors.newFixedThreadPool(1);
        // 执行FutureTask,相当于上例中的client.request("a") 发送请求
        // 在这里开启线程进行RealData的call()执行
        executor.submit(futureTask);
        System.out.println("请求完毕");
        try {
            // 这里依然可以做额外的数据操作,这里使用sleep代替其他业务逻辑的处理
            Thread.sleep(1000);
            System.out.println("其他操作");
        } catch (InterruptedException e) {

        }
        // 相当于上例中的data,getResult(),取得call()方法的返回值
        // 如果此时call()方法没有执行完成,则依然会等待
        System.out.println("真实数据:" + futureTask.get());
    }
}
// 结果
请求完毕
// 等待一秒后
其他操作
// 等待一段时间
真实数据:a a a a a a a a a a 
注:
  1. JDK内置的Future模式功能强大,除了基本的功能外,它还可以取消Future任务,或者设定Future任务的超时时间
  2. Future模式的核心在于去除了主函数中的等待时间,并使得原本需要等待的时间段可以用于处理其他业务逻辑,从而充分利用计算机资源
版权声明:本文为qq_44039966原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_44039966/article/details/103324170

智能推荐

Windows系统下正确安装MongoDB

1、下载、安装 官网下载: http://www.mongodb.org/downloads 下载好之后,接下来进行安装了: 2、创建数据文件夹 MongoDB将数据文件夹存储在 db 文件夹下。 可是这个数据文件夹不会主动创建,我们在安装完毕后须要创建它。请注意。数据文件夹应该放在根文件夹下((如: C:\ 或者 D:\ 等 )。  在本教程中。我们已经在C:盘 安装了 mo...

DC-8靶机教程

多喝热水:http://hackergu.com/ 一、主机发现 ​ 使用netdiscover,发现主机IP为192.168.203.134。 二、端口扫描 ​ 目标开放22端口和80端口,web站点是一个DrupalCMS的站点,版本为7。 三、漏洞利用 ​ 我们访问此站点: ​ 关注点为两个红框,如果点击上面那个红框的内容,url显示为: ​ 倘若点击的是下面的红框,则url显示为: ​ 看...

Android VIEW简单绘图

画布canvas 提供两个构造函数: Canvas():创建一个空的Canvas对象 Canvas(Bitmap bitmap):创建一个以bitmap位图为背景的Canvas 常用的Canvas方法: void drawBitmap(Bitmap bitmap,float left,float top,Paint paint):在指定坐标绘制位图 void drawLine(float star...

数据去重的各种方法汇总(三)

Pandas去重DataFrame 接上篇,这篇就剩最后的用Pandas对DataFrame里面的数据去重,这里也有两种方法,可以直接看官方文档(但是掌柜觉得其实就是一种方法,因为第一种只是显示哪些重复。。。): 使用duplicated方法,它会返回一个布尔向量,其长度为行数,表示行是否重复,但是并不会删除重复数据。来看实例: 然后会得到这样一个表格: 现在使用duplicated方法: 会得到...

Linux环境下RabbitMQ消息队列的安装和配置

一、什么是RabbitMQ? RabbitMQ就是一个在AMQP基础上实现的企业级消息系统,简单的说,就是一个消息队列系统。具体的介绍,可以网上去搜!目前只介绍RabbitMQ在Linux系统的安装。 二、RabbitMQ的安装 1、 RabbitMQ是基于Erlang开发,所以使用之前必须安装Erlang语言开发包。 wget http://www.erlang.org/download/otp...

猜你喜欢

train_test_split切分数据集工具

顾名思义,这是一个切分训练集与测试集的工具   如果我们不使用,而是手动进行划分,要么进行简单的操作——划去前80%为训练集,后20%为测试集,这样会带来很多的问题,因为这样做,我们切出来的会让训练集和测试集的分布很不一致,我们可以看一下简单粗暴方法切出来的分布图: 红色的训练集,蓝色是测试集(点击图片放大可以看得很清楚,直接看博客好像图片模糊)   然...

shell编程第一节 和shell

shell编程看的博客感觉写的挺好的:http://www.cnblogs.com/dongying/p/6262935.html 以及  https://www.cnblogs.com/clsn/p/8028337.html#auto_id_0 简单总结:shell编程就是对一堆Linux命令的逻辑化处理。 chmod +x hello_world.sh ./hello_world.s...

微信开发:js sdk 分享(java)

今天记录一下微信jssdk 的分享给朋友的功能,获取config接口注入。 1.需要绑定域名(注意:设置js安全域名的时候,需要设置微信ip白名单,ip白名单新出来的,非白名单内的ip无法获取access_token 更无法获取jsapi) 在设置js 安全域名在 设置–>公众号设置–>功能设置里边 appid appSercret 在开发–>...

js--HTML美术馆

前言 因为之前库房合作的时候交给我一个任务,就是在点击某一项物品的时候显示出几张相印的小图片,然后点击小图片之后显示出一张大图片,因为当时还没有接触JavaScript,所以这方面的知识不是很了解,一直拖着,大概有两天吧,是在是解决不了,于是将这个任务交给了老付和建华,今天在学习JavaScript的时候突然之后就看到了有这么一项功能,于是就有感而发。 内容 首先向大家展示代码。 这部分是HTML...

Jenkins持续集成环境部署(入门篇)

为什么要持续集成 持续集成是一种软件开发实践,即团队开发成员经常集成它们的工作,通过每个成员每天至少集成一次,也就意味着每天可能会发生多次集成。每次集成都通过自动化的构建(包括编译,发布,自动化测试)来验证,从而尽早地发现集成错误。 价值: 1、减少风险 一天中进行多次的集成,并做了相应的测试,有利于检查缺陷,了解软件的健康状况,减少假定。 2、减少重复过程 节省时间、费用和工作量,通过自动化的持...