学而时习之,不亦说乎!
--《论语》
什么是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模式的类和接口,但是那些都是用起来简单,实现很复杂,重在理解。