一、什么是Future模式:
Future设计模式是Java多线程开发常用设计模式。一句话,将客户端请求的处理过程从同步改为异步,以便将客户端解放出来,在服务端程序处理期间可以去干点其他事情,最后再来取请求的结果。好处在于整个调用过程中不需要等待,可以充分利用所有的时间片段,提高系统的响应速度。
这里就以java.util.concurrent.Future为例,简单说一下Future的具体工作方式。Future对象本身可以看作是一个显式的引用,一个对异步处理结果的引用。由于其异步性质,在创建之初,它所引用的对象可能还并不可用(比如尚在运算中,网络传输中或等待中)。这时得到Future的程序流程如果并不急于使用Future所引用的对象,那么它可以做其它任何想做的事儿,当流程进行到需要Future背后引用的对象时,可能有两种情况:
- 希望能看到这个对象可用,并完成一些相关的后续流程。如果实在不可用,也可以进入其它分支流程。
- “没有你我的人生就会失去意义,所以就算海枯石烂,我也要等到你。”(当然,如果实在没有毅力枯等下去,设一个超时也是可以理解的)
第一种情况,可以通过调用Future.isDone()判断引用的对象是否就绪,并采取不同的处理;
第二种情况,则只需调用get()或get(long timeout, TimeUnit unit)通过同步阻塞方式等待对象就绪。实际运行期是阻塞还是立即返回就取决于get()的调用时机和对象就绪的先后了。(如下图所示)
二、Future模式举例
Data接口类
public interface Data {
String getResult() throws InterruptedException;
}
FutureData实现类
public class FutureData implements Data {
RealData realData = null; // FutureData是RealData的封装
boolean isReady = false; // 是否已经准备好
public synchronized void setRealData(RealData realData) {
if (isReady)
return;
this.realData = realData;
isReady = true;
notifyAll(); // RealData已经被注入到FutureData中了,通知getResult()方法
}
@Override
public synchronized String getResult() throws InterruptedException {
if (!isReady) {
System.out.println("还没有好,还需等待!");
wait(); // 一直等到RealData注入到FutureData中
System.out.println("好了");
}
return realData.getResult();
}
}
RealData 实现类
public class RealData implements Data {
protected String data;
public RealData(String data) {
// 利用sleep方法来表示RealData构造过程是非常缓慢的
try {
System.out.println("RealData生成中...");
Thread.sleep(4000);
}
catch (InterruptedException e) {
e.printStackTrace();
}
this.data = data;
}
@Override
public String getResult() {
return data+"-RealData";
}
}
Client 客户端类
public class Client {
public Data request(final String string) {
final FutureData futureData = new FutureData();
new Thread(new Runnable() {
@Override
public void run() {
// RealData的构建很慢,所以放在单独的线程中运行
RealData realData = new RealData(string);
futureData.setRealData(realData);
}
}).start();
System.out.println("先直接返回FutureData");
return futureData; // 先直接返回FutureData
}
}
Test调用测试
public class Test{
public static void main(String[] args) throws InterruptedException {
Client client = new Client();
// 这里会立即返回,因为获取的是FutureData,而非RealData
System.out.println("请求数据");
Data data = client.request("name");
// 这里可以用一个sleep代替对其他业务逻辑的处理
System.out.println("等待的时间里面,干点其它事情");
Thread.sleep(2000);
System.out.println("其它事情干完了,看看是否有数据返回?");
// 使用真实数据
System.out.println("真实数据返回(如果还没有返回堵塞等待)=" + data.getResult());
}
}
测试结果
请求数据
先直接返回FutureData
等待的时间里面,干点其它事情
RealData生成中...
其它事情干完了,看看是否有数据返回?
还没有好,还需等待!
好了
真实数据返回(如果还没有返回堵塞等待)=name-RealData
三、Future模式的JDK内置实现
由于Future是非常常用的多线程设计模式,因此在JDK中内置了Future模式的实现。这些类在java.util.concurrent包里面。其中最为重要的是FutureTask类,它实现了Runnable接口,作为单独的线程运行。在其run()方法中,通过Sync内部类调用Callable接口,并维护Callable接口的返回对象。当使用FutureTask.get()方法时,将返回Callable接口的返回对象。同样,针对上述的实例,如果使用JDK自带的实现,则需要作如下调整。
首先,Data接口和FutureData就不需要了,JDK帮我们实现了。
RealData改成RealDataJdk
import java.util.concurrent.Callable;
public class RealDataJdk implements Callable<String> {
protected String data;
public RealDataJdk(String data) {
this.data = data;
}
@Override
public String call() throws Exception {
// 利用sleep方法来表示真是业务是非常缓慢的
try {
System.out.println("RealData生成中...");
Thread.sleep(1000);
}
catch (InterruptedException e) {
e.printStackTrace();
}
return data + "-RealData";
}
}
Test 改成 TestJdk
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
public class TestJdk {
public static void main(String[] args)
throws InterruptedException, ExecutionException {
FutureTask<String> futureTask = new FutureTask<String>(new RealDataJdk("name"));
ExecutorService executor = Executors.newFixedThreadPool(1); // 使用线程池
// 执行FutureTask,相当于上例中的client.request("name")发送请求
executor.submit(futureTask);
// 这里可以用一个sleep代替对其他业务逻辑的处理
// 在处理这些业务逻辑过程中,RealData也正在创建,从而充分了利用等待时间
System.out.println("数据=" + futureTask.get());
Thread.sleep(2000);
// 使用真实数据
// 如果call()没有执行完成依然会等待
System.out.println("数据=" + futureTask.get());
}
}