它的核心是异步调用,如果我们不着急要结果,可以让被调用者立刻返回,随后让它在后台慢慢处理这个请求,对于调用者来说则可以处理其他任务,在真正需要数据的场合再去尝试获得需要的结果。
1)Future模式的主要角色
参与者 | 作用 |
Main | 系统启动,调用Client发出请求 |
Client | 返回Data对象,立即返回FutureData,并开始ClientThread线程装配RealData |
Data | 返回数据的接口 |
FutureData | Future数据,构造很快,但是一个虚拟的数据,需要装配RealData |
FutureRealData | 真实的数据,其构造比较慢 |
它的核心结构如图所示。
2)Future模式的简单实现
在这个实现中,有一个核心接口Data,这就是客户端希望获取的数据。在Future模式中,这个Data接口有两个重要的实现,分别是RealData,也就是真是数据,这就是我们最终需要获得的,有价值的信息。另外一个就是FutureData,它就是用来提取RealData的一个“订单”。因此FutureData是可以立即返回得到。
下面是Data接口:
public interface Data {
public String getResult();
}
FutureData实现了一个快速返回的RealData包装。它只是一个包装,或者说是一个RealData的虚拟实现。因此,它可以很快被构造并返回。当使用RealData准备好并注入到FutureData中,才最终返回数据。
//FutureData是Future模式的关键,它实际上是真实数据RealData的代理,封装了获取RealData的等待过程
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) {
wait(); //一直等到RealData注入到FutureData中
}
return realData.getResult();
}
}
RealData是最终需要使用的数据模型。它的构造很慢。在这里,使用sleep()函数模拟这个过程,简单地模拟一个字符串的构造。
public class RealData implements Data {
protected String data;
public RealData(String data) {
//利用sleep方法来表示RealData构造过程是非常缓慢的
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.data = data;
}
@Override
public String getResult() {
return data;
}
}
接下来就是我们的客户端程序,Client主要实现了获取FutureData,并开启构造RealData的线程。并在接受请求后,很快的返回FutureData。注意,它不会等待将数据真的构造完毕再返回,而是立即返回FutureData,即使这个时候FutureData并没有真实数据。
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();
return futureData; //先直接返回FutureData
}
}
最后,就是主函数Main,它主要负责调用Client发起请求,并消费返回的数据。
public static void main(String[] args) {
Client client = new Client();
Data data = client.request("name");
System.out.println("请求完毕");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
}
System.out.println("数据 = " + data.getResult());
}
3)JDK中的Future模式
Future模式的基本结构,其中Future接口就类似于订单或者说是契约。通过它,可以得到真实的数据。RunnableFuture继承了Future和Runnable两个接口,其中run()方法用于构造真实的数据。它有一个具体的实现FutureTask类。FutureTask有一个内部的Sync,一些实质性工作,会委托Sync类实现。而Sync类最终会调用Callable接口,完成实际数据的组装工作。
Callable()接口只有一个方法call(),它会返回需要构造的实际数据。这个Callable接口也是这个Future框架和应用程序之间的重要接口。如果我们要实现自己的业务系统,通常需要实现自己的Callable对象。此外,FutureTask类也与应用程序密切相关,通常,我们会使用Callable实例构造一个FutureTask实例,并将它提交给线程池。
下面将展示内置的Future模式的使用:
public class JRealData implements Callable<String> {
private String para;
public JRealData(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 {
Thread.sleep(100);
} catch (InterruptedException e) {
}
}
return sb.toString();
}
}
上述代码实现了Callable接口,它的call()方法会构造我们需要的真实数据并返回。当然这个过程可以是缓慢的,这里使用Thread.sleep()模拟它:
public class FutureMain {
public static void main(String[] args) throws InterruptedException, ExecutionException {
FutureTask<String> future = new FutureTask<>(new JRealData("a"));
ExecutorService executor = Executors.newFixedThreadPool(1);
executor.submit(future);
System.out.println("请求完毕");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
}
System.out.println("数据 = " + future.get());
}
}
上述代码就是使用Future模式的典型。第4行,构造了FutureTask对象实例,表示这个任务是有返回值的。构造FutureTask时,使用Callable接口,告诉FutureTask我们需要的数据应该如何产生。接着,第8行,将FutureTask提交给线程池。显然,作为一个简单任务的提交,这里必然是立即返回的,因此程序不会阻塞。接下来,不用关心数据是如何产生的。可以去做一些额外的事情,然后在需要的时候就可以通过Future.get()得到实际的数据。
除了基本的功能外,JDK还为Future接口提供了一些简单的控制功能:
boolean cancel(boolean mayInterruptIfRunning); //取消任务
boolean isCancelled(); //是否已经取消
boolean isDone(); //是否已经完成
V get() throws InterruptedException,ExecutionException //取得返回对象
V get(long timeout, TimeUnit unit); //取得返回对象,可以设置超时时间