• 我理解的Future模式


    学而时习之,不亦说乎!

                                 --《论语》

    什么是Future?

      考虑一个场景,为了完成某个业务,我需要同时查询三张表的三条独立数据。但是呢,这三张表数据量很大,三条数据分别需要消耗4s,6s,8s才能查询出来。在不考虑其他耗时的情况下,按顺序查出这三条数据,需要消耗18s时间。因为这三条数据其实是无上下文关系的,我们可以想到,如果我使用三个线程同时进行查询,那么会消耗多少时间呢?应该是耗时最长的那条数据所需的时间8s。那么,这儿分别实现这两种方式的查询。

    项目整体结构如下:

    entity包是通用实体类,normal包下是普通方式的查询代码,future包下是使用future模式查询代码。

    1.创建一个普通的java项目。

    2.创建一个通用实体类User:

    package com.zby.entity;
    
    public class User {
        private String username;
        private String password;
    
        public String getUsername() {
            return username;
        }
    
        public void setUsername(String username) {
            this.username = username;
        }
    
        public String getPassword() {
            return password;
        }
    
        public void setPassword(String password) {
            this.password = password;
        }
    
        @Override
        public String toString() {
            return "User [username=" + username + ", password=" + password + "]";
        }
    
    }

    3.使用普通方式按顺序查询的代码:

    创建一个UserDao接口:

    package com.zby.normal.dao;
    
    import com.zby.entity.User;
    
    public interface UserDao {
        User queryUserByUsername(String username);
    }

    创建UserDao的实现类UserDaoImpl:

    package com.zby.normal.dao.impl;
    
    import com.zby.entity.User;
    import com.zby.normal.dao.UserDao;
    
    public class UserDaoImpl implements UserDao {
        @Override
        public User queryUserByUsername(String username) {
            try {
                System.out.println("开始对参数【" + username + "】进行数据库查询.....");
                // 模拟数据库操作耗时
                int sleepTime = username.length() * 1000;
                Thread.sleep(sleepTime);
                // 模拟数据库返回数据
                User user = new User();
                user.setUsername(username);
                user.setPassword("123456");
                System.out.println("完成对参数【" + username + "】进行数据库查询.....耗时" + sleepTime + "毫秒");
                return user;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return null;
        }
    
    }

    测试类:

    package com.zby.normal;
    
    import com.zby.entity.User;
    import com.zby.normal.dao.UserDao;
    import com.zby.normal.dao.impl.UserDaoImpl;
    
    public class GeneralApplication {
    
        public static void main(String[] args) {
            long startTime = System.currentTimeMillis();
            UserDao queryDao = new UserDaoImpl();
            User lisi = queryDao.queryUserByUsername("lisi");
            User wangwu = queryDao.queryUserByUsername("wangwu");
            User zhangsan = queryDao.queryUserByUsername("zhangsan");
            System.out.println("lisi:" + lisi);
            System.out.println("wangwu:" + wangwu);
            System.out.println("zhangsan:" + zhangsan);
            System.out.println("操作总耗时" + (System.currentTimeMillis() - startTime) + "毫秒");
        }
    
    }

    控制台输出:

    开始对参数【lisi】进行数据库查询.....
    完成对参数【lisi】进行数据库查询.....耗时4000毫秒
    开始对参数【wangwu】进行数据库查询.....
    完成对参数【wangwu】进行数据库查询.....耗时6000毫秒
    开始对参数【zhangsan】进行数据库查询.....
    完成对参数【zhangsan】进行数据库查询.....耗时8000毫秒
    lisi:User [username=lisi, password=123456]
    wangwu:User [username=wangwu, password=123456]
    zhangsan:User [username=zhangsan, password=123456]
    操作总耗时18001毫秒

    这个就很简单了,就是一般的数据库操作。要使用并发查询,需要考虑什么呢?

      首先,dao是要改的,我们不能在查询时直接返回实体类对象,因为我们的查询不是实时的,没法直接获取到结果。那我们这儿就考虑使用一个类,封装返回的数据,提供一个get方法,在需要的时候才获取需要的结果数据;提供一个set方法,使用另外的线程去查询结果,查询完毕后使用set到里面去,这样get的时候就有数据了。但是,如果我在使用get的时候数据还没有获取完成,这时怎么办?这儿就需要使用线程的阻塞唤醒机制:当结果还在获取中调用了get方法,会阻塞当前线程,等待获取完成后唤醒get。

     4.使用Future模式的查询代码:

    封装数据的FutureData类:

    package com.zby.future.data;
    
    public class FutureData<T> {
        private T data;
    
        public synchronized void setData(T data) {
            this.data = data;
            notify();
        }
    
        public synchronized T getData() {
            while (null == data) {
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            return this.data;
        }
    
    }

    改造后的UserDao接口:

    package com.zby.future.dao;
    
    import com.zby.entity.User;
    import com.zby.future.data.FutureData;
    
    public interface UserDao {
        FutureData<User> queryUserByUsername(String username);
    }

    改造后的UserDao接口实现类UserDaoImpl:

    package com.zby.future.dao.impl;
    
    import com.zby.entity.User;
    import com.zby.future.dao.UserDao;
    import com.zby.future.data.FutureData;
    
    /**
     * 
     * @描述:
     * @作者: zby
     * @创建时间: 2017年9月13日
     */
    public class QueryDaoImpl implements UserDao {
    
        @Override
        public FutureData<User> queryUserByUsername(final String username) {
            final FutureData<User> data = new FutureData<User>();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        System.out.println("开始对参数【" + username + "】进行数据库查询.....");
                        // 模拟数据库操作耗时
                        int sleepTime = username.length() * 1000;
                        Thread.sleep(sleepTime);
                        // 模拟数据库返回数据
                        User user = new User();
                        user.setUsername(username);
                        user.setPassword("123456");
                        System.out.println("完成对参数【" + username + "】进行数据库查询.....耗时" + sleepTime + "毫秒");
                        data.setData(user);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
            return data;
        }
    }

    测试类:

    package com.zby.future;
    
    import com.zby.entity.User;
    import com.zby.future.dao.UserDao;
    import com.zby.future.dao.impl.QueryDaoImpl;
    import com.zby.future.data.FutureData;
    
    public class FutureApplication {
    
        public static void main(String[] args) {
            long startTime = System.currentTimeMillis();
            UserDao queryDao = new QueryDaoImpl();
            FutureData<User> lisi = queryDao.queryUserByUsername("lisi");
            FutureData<User> wangwu = queryDao.queryUserByUsername("wangwu");
            FutureData<User> zhangsan = queryDao.queryUserByUsername("zhangsan");
            System.out.println("lisi:" + lisi.getData());
            System.out.println("wangwu:" + wangwu.getData());
            System.out.println("zhangsan:" + zhangsan.getData());
            System.out.println("耗时" + (System.currentTimeMillis() - startTime));
        }
    
    }

    控制台输出:

    开始对参数【lisi】进行数据库查询.....
    开始对参数【wangwu】进行数据库查询.....
    开始对参数【zhangsan】进行数据库查询.....
    完成对参数【lisi】进行数据库查询.....耗时4000毫秒
    lisi:User [username=lisi, password=123456]
    完成对参数【wangwu】进行数据库查询.....耗时6000毫秒
    wangwu:User [username=wangwu, password=123456]
    完成对参数【zhangsan】进行数据库查询.....耗时8000毫秒
    zhangsan:User [username=zhangsan, password=123456]
    耗时8002

    可以看到,这儿查询是并发开始的,查询时间得到了极大的提升,瓶颈仅仅是最耗时的那个查询。代码很简单,但是需要理解。

       在Java中也有一系列的例如java.util.concurrent.Future<V>,java.util.concurrent.Callable<V>,java.util.concurrent.Executors等一系列可以实现Future模式的类和接口,但是那些都是用起来简单,实现很复杂,重在理解。

  • 相关阅读:
    docker重启提示已存在一个容器的问题处理
    conda虚拟环境安装
    dell5460笔记本电脑ubuntu18.04系统音频驱动的安装和使用
    CentOS8的网络管理变化
    jupyter notebook 安装扩展nbextensions
    win10系统systeminfo命令的过滤用法
    《学习scrapy框架爬小说》的进一步完善
    记手机端 下拉加载新数据
    记 页面使用overflow-scroll在iOS上滑动卡顿的问题
    记 判断手机号运营商function
  • 原文地址:https://www.cnblogs.com/zby9527/p/7519349.html
Copyright © 2020-2023  润新知