问题引出
现在我们想要炒一道菜,但是我们没有厨具和菜,现在我们从网上订购了一套厨具,但在厨具送来的期间,我们不必一直等到厨具到来,而是可以先去买菜,然后厨具到了之后直接开始炒菜
这就是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
注:
- JDK内置的Future模式功能强大,除了基本的功能外,它还可以取消Future任务,或者设定Future任务的超时时间
- Future模式的核心在于去除了主函数中的等待时间,并使得原本需要等待的时间段可以用于处理其他业务逻辑,从而充分利用计算机资源