Future模式是一种并行设计模式,原理是当你申请资源时,立即返回一个虚拟的资源(通常这个时候在后台异步去申请真正资源),当真正需要使用资源的时候,再将对虚拟资源的调用传递给成真正的资源(如果这个时候真正资源依然没有申请到,则阻塞)。
当一个操作耗时比较多时,可以将耗时操作拆分成多个独立的不太耗时的子操作,使子操作并行执行,各子操作异步保存执行结果以供主操作随时使用。
Future模式可用于服务端异步回调,将耗时的操作并行化,再通过回调方式将结果合并。
Future构造时生成了虚拟的结果,使用这个结果越晚就越不容易阻塞,所以,从生成虚拟资源到真正使用资源的间隔越大,Future模式的功效越大。
场景:一个业务流程有三步,1:从网络获取10个数据(2s);2:读取本地文件到内存(1s),检查是否包含获取到的数据,将没有包含的保存到文件;3:计算新文件的总大小(1s)。
分析可知:“本地文件到内存”与“网络获取10个数据”是两个耗时的操作,他们彼此是独立的(只有在检查是否包含的时候才有交集),因此可以并行操作,"计算新文件的总大小"也是一个耗时操作,然而它依赖第二步的完成,因此它必需和第二步同步执行,不能并行。
设计:
示例代码:
import java.util.concurrent.*;
interface Data {
String getData();
}
class NetData implements Data {
private String data;
public NetData() {
try {
Thread.sleep(2 * 1000);
} catch (InterruptedException e) {
}
this.data = "真实数据";
}
@Override
public String getData() {
return data;
}
}
class FutureData implements Data {
private String realData;
private volatile boolean isReady;
public FutureData() {
// 异步准备数据
new Thread(new Runnable() {
@Override
public void run() {
String realData = new NetData().getData();// 耗时操作
synchronized (FutureData.this) {
if (isReady) return;
isReady = true;
FutureData.this.realData = realData;
FutureData.this.notifyAll();// 不能直接notifyAll(),必需和synchronized (FutureData.this)保持一致
}
}
}).start();
}
@Override
public synchronized String getData() {
// 使用while,防止唤醒后数据还未准备好
while (!isReady) {// 使用while,防止唤醒后数据还未准备好
try {
wait();
} catch (InterruptedException e) {}
}
return realData;
}
}
/**
* Java标准库已经内置了 Future 模式的实现,通过FutureTask和Callable API实现
* 其内部机制自动保证同步和阻塞,无需关心上文FutureData中的同步及wait/notify机制等问题,非常简洁
* 而且具有异步任务取消等功能,如:
*/
class JDKFutureTask implements Data {
private FutureTask<String> futureTask;
public JDKFutureTask() {
futureTask = new FutureTask<>(new Callable<String>() {// 需要的数据类型是String,使用泛型实现!
@Override
public String call() throws Exception {
String realData = new NetData().getData();// 耗时操作
return realData;
}
});
ExecutorService executorService = Executors.newFixedThreadPool(1);
executorService.submit(futureTask);
executorService.shutdown();
}
@Override
public String getData() {
String data = null;
try {
data = futureTask.get();
} catch (Exception e) {}
return data;
}
}
// Others ...
class FileLoader {
String loadFile() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {}
return "abc";
}
}
class FileCounter {
int countFile() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {}
return 0;
}
}
public class Test {
private static void doTask(Data data) {// 这里 data,只是先保留一个资源引用,尚未真正使用资源,可以并行化
String file = new FileLoader().loadFile();
if (file.contains(data.getData())) {// 在这里,data.getData() 才是真正要使用资源
// Nop
}
int countFile = new FileCounter().countFile();
}
// 自定义的Future方式和JDK FutureTask 方式耗时基本相同,而普通编程方式耗时最长。
public static void main(String[] args) throws Exception {
// 普通方式,原始阻塞方式
new Thread(new Runnable() {
@Override
public void run() {
long s = System.currentTimeMillis();
doTask(new NetData());
System.out.println("使用普通方式耗时:" + (System.currentTimeMillis() - s));
}
}).start();
// Future设计模式
new Thread(new Runnable() {
@Override
public void run() {
long s = System.currentTimeMillis();
doTask(new FutureData());
System.out.println("使用 Future设计模式耗时:" + (System.currentTimeMillis() - s));
}
}).start();
// JDK FutureTask 方式
new Thread(new Runnable() {
@Override
public void run() {
long s = System.currentTimeMillis();
doTask(new JDKFutureTask());
System.out.println("使用JDK FutureTask 方式耗时:" + (System.currentTimeMillis() - s));
}
}).start();
}
}
打印结果:
使用 Future设计模式耗时:3003
使用JDK FutureTask 方式耗时:3006
使用普通方式耗时:4004