Future 设计模式核心原理图:
client端通过 FutureData 发送一个执行耗时操作的请求,FutureData 则直接返回一个回调接口的引用(Data 接口,用于返回获取到的真正结果),然后在 FutureData 内部再另起一个线程去执行真正的耗时操作。当 client 端 执行了获取结果的方法时,如果执行完成,则返回结果。如果还在执行中,则会进入线程等待状态,一直等到执行完成进行线程唤醒之后才能拿到结果。
这个设计模式在 Java JDK 中已经被实现了,这里先看一下自测代码,从最里层开始看:
自测 Future 模式实现
- RealData 实现类
public class RealData {
private String data;
public RealData(String data) throws InterruptedException {
System.out.println("模拟数据加载中。。。");
TimeUnit.SECONDS.sleep(5);
this.data = data;
}
public String getResult() {
return data + " - real data";
}
}
上面流程图中标注的 RealData 也要实现 Data 接口,这里自己实现的话,其实也是可以省略的,自己写一个获取方法,效果也是一样的。这里模拟的是执行耗时操作。
- FutureData 实现类
public class FutureData implements Data {
private boolean isReady;// 是否准备好了。 也就是耗时操作是否执行完成了。
private RealData data;// 真实数据操作对象
public synchronized void setRequest(RealData data) {
if (isReady) {
return;
}
this.data = data;
isReady = true;
notify();
}
@Override
public synchronized String getResult() throws InterruptedException {
if (!isReady) {
System.out.println("等待数据返回中。。。");
wait();
}
return data.getResult();
}
}
这里说一下,因为 RealData 的耗时操作直接在构造方法中写的,所以,当进入 setRequest 方法的时候,说明 RealData 已经实例化好了,也就是耗时操作已经执行完成了。这里 FutureData 也就是 RealData 的一个代理,包装着 RealData 的耗时操作过程。
wait() 和 notify() 方法 一定要配合 synchronize 关键字使用。
- Client 端实现
public class Client {
public Data setRequest(String requestString) {
FutureData data = new FutureData();
new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println("client run : " + System.currentTimeMillis());
data.setRequest(new RealData(requestString));// 当 RealData 的构造方法执行完成之后,才会进入到 setRequest的方法中。
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
return data;
}
}
- 调用代码
public class Main {
/**
* Futrue 设计模式,
* client端 发送某个耗时请求 - > FutrueData 代理对象 先返回一个 假对象,然后其内部开启线程进行真实请求操作 -- > RealData 真实处理对象,处理结束之后,回调数据给 代理对象,代理对象再进行通知 client端
*/
public static void main(String[] args) throws InterruptedException, ExecutionException {
Client client = new Client();
Data data = client.setRequest("this is test");
System.out.println("main : " + System.currentTimeMillis());
System.out.println(data.getResult()); // 会进入线程等待状态。
}
}
JDK 内部 Future 实现
上面也说了,这个设计模式 JDK 内部是已经实现了这个机制。 FutureTask 作为我们的代理类,它实现了 Runnable 接口,本身就作为一个独立的线程执行,但是它没有start 方法,必须配合线程池使用。因为 FutureTask 的传入对象是 Callable(接口) 类型,所以,我们的 RealData 类也需要实现 Callable 接口。真实数据最终会通过 Callable 接口的 call() 方法传递给 FutureTask 。
- 实现代码:
public class RealData_1 implements Callable<String> {
private String data;
public RealData_1(String data) {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("real data 1");
this.data = data;
}
@Override
public String call() throws Exception {
System.out.println("real data 1 call()");
return data;
}
}
- 调用方式:
public static void main(String[] args) throws InterruptedException, ExecutionException {
// Client client = new Client();
// Data data = client.setRequest("this is test");
// System.out.println("main : " + System.currentTimeMillis());
// System.out.println(data.getResult());
/**
* Java jdk 内置 FutureTask 是一个线程类,必须使用配合线程池调用。
*/
FutureTask futureTask = new FutureTask(new RealData_1("this is test jdk future"));
ExecutorService exe = Executors.newFixedThreadPool(1);
exe.submit(futureTask);
System.out.println(futureTask.get());
}
最后说一下线程池的 submit 方法和 execute 方法的区别:
- submit 可以传入实现 Callable接口的实例对象。
- submit 方法有返回值。