我理解的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模式的类和接口,但是那些都是用起来简单,实现很复杂,重在理解。

posted @ 2017-09-14 10:28  java拌饭  阅读(539)  评论(0编辑  收藏  举报