Future模式

该模式的主要思想是:当你调用方法后,会开启新线程运行方法体,方法体可能不会马上计算出真正结果,但是方法会先给你返回一个值,让你不必一直守在这里等待,你可以去办其它事情,等未来真正需要方法体计算出的结果的时候,再尝试获取。

非java.util.Concurrent包实现

首先创建一个接口IData:

1
2
3
interface IData {
String getContent();
}

FutureData和RealData都实现了这个接口。一开始我们需要的是RealData,但是RealData并没有创建好,没关系,此时并不着急使用RealData提供的数据,这时候返回一个FutureData,它就像RealData的代理一样,等“有朝一日”,需要真正数据的时候,就会用FutureData代理RealData的方法,如果这个时候RealData还是没有生成数据,那么就只有等待了。按照这个思路,接下来实现RealData,在构造器中添加睡眠函数模拟创建需要时间消耗的过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class RealData implements IData {
private String workout;
public RealData() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
workout = "计算出了真正的结果";
}
@Override
public String getContent() {
return workout;
}
}

FutureData的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class FutureData implements IData {
private IData data;
private boolean ready = false;
public synchronized void setData(IData data) {
if (ready) {
return;
}
this.data = data;
ready = true;
notifyAll();
}
@Override
public synchronized String getContent() {
while (!ready) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return data.getContent();
}
}

在main函数的执行过程中,setData总是先行调用,第一次调用的时候ready为false,所以等待RealData实例的创建,如果这个创建一直没有完成,那么setData()就一直拿着锁,只要调用getContent()就会一直处于waiting状态。一旦RealData实例创建完成,ready为true此时FutureData的getContent()返回真正的结果。

代理客户端执行类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Host {
public IData request() {
final FutureData future = new FutureData();
new Thread() {
@Override
public void run() {
RealData realData = new RealData();
future.setData(realData);
}
}.start();
return future;
}
}

main方法:

1
2
3
4
5
public static void main(String[] args) {
Host host = new Host();
IData iData = host.request();
System.out.println(iData.getContent());
}

至此,一个简单的Future模式就手工完成了。

接下来把以上代码改写为java.util.Concurrent包实现。

java.util.Concurrent包实现

在Java中java.util.Concurrent包提供了相应的类,对一些核心操作进行了封装,例如不需要FutureData手动写同步方法,只需要去继承FutureTask类,此类是一个继承了Future接口的标准类。

创建FutureData02类,它是前文中FutureData的改写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class FutureData02 extends FutureTask<RealData> implements IData {
public FutureData02(Callable<RealData> callable) {
super(callable);
}
@Override
public String getContent() {
String result = null;
try {
result = get().getContent();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
return result;
}
}

和之前实现FutureData类不同的地方在于,不需要写同步方法,要想获得RealData实例,必须首先调用FutureTask提供的get()。

接着就是代理客户端执行类的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Host02 {
public IData request() {
FutureData02 future02 = new FutureData02(new Callable<RealData>() {
@Override
public RealData call() {
RealData realData = new RealData();
return realData;
}
});
new Thread(future02).start();
return future02;
}
}

Callable可以理解为有返回值的Runnable,想想之前的get(),然后就能体会Callable返回的实例realData。在最后启动线程的时候,传入的是future02,实际上它的父类FutureTask实现了Runnable接口。

小结

这里用两种方式,实现了简单的Future模式。

其他问题

这里出现了内部匿名类引用外部方法的局部变量必须声明为final的问题,除此之外,引用外部作用域内的局部变量和外部方法的参数也要声明为final,Java 8增加的Effectively final功能聚焦的也是这个问题。可以参考链接:

也可利用可变对象,构造大小为1的数组来规避。