什么是Future模式?
Future模式是多线程开发中一种经典的设计模式,其核心思想是异步调用。它通过将耗时操作与主线程分离,让主线程不必等待结果返回即可继续执行其他任务,从而显著提升系统的响应速度和资源利用率。
同步调用
异步调用
实现了Future模式的客户端在拿到这个返回结果后,并不急于处理,⽽是调⽤了其他业务逻辑,充分利⽤了等待时间,这就是Future模式的核心所在。在完成了其他业务逻辑的处理后,再使⽤返回⽐较慢的Future数据。
Future模式的主要参与者包括:
- Main:系统入口,调用
Client发起请求,处理其他业务,最终通过Future获取真实结果 - Client:接收请求后,立即返回
FutureData,同时启动独立线程异步构建RealData - Data:结果返回接口,定义获取真实结果的方法(如
getResult()) - FutureData:虚拟结果实现类,封装
RealData的构建等待逻辑,是Future模式的核心代理 - RealData:真实结果实现类,构造过程耗时(如数据计算、IO操作),最终通过
getResult()返回
Future模式的简单实现
核心接口Data
Data就是客户端希望获取的数据,在Future模式中,Data接口有两个重要的实现:
RealData:最终获取的真实数据FutureData:用于提取RealData
public interface Data<T> {
public T getResult();
}
实现类FutureData
FutureData实现了一个可以快速返回的RealData包装,它只是一个虚拟实现,因此很快就可以返回。当使用FutureData的getResult方法的时候,如果实际的数据还没有准备好,就会被阻塞,等待RealData准备好并注入FutureData中才最终返回。
public class FutureData<T> implements Data<T> {
protected RealData<T> realData; //FutureData是RealData的包装
protected boolean isReady;
@Override
public synchronized T getResult() {
while(!isReady){
try{
this.wait(); //数据还没准备好,线程阻塞,直到被唤醒
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
return realData.getResult();//返回真实数据
}
public synchronized void setRealData(RealData realData) {
if (isReady){
return ;
}
this.realData=realData;
this.isReady=true;
this.notifyAll(); //RealData已经被注入,唤醒所有的阻塞线程
}
}
实现类RealData
RealData是真实的数据,是最终需要使用的数据模型,它的构造非常慢。
public class RealData<T> implements Data<T> {
private T data;
public RealData(T data) throws InterruptedException {
for (int i=0;i<10;i++){//使用循环睡眠来代替耗时操作
System.out.println("正在构造数据...");
TimeUnit.SECONDS.sleep(1);
}
this.data=data;
}
@Override
public T getResult() {
return data;
}
}
客户端程序
Client实现了获取FutureData并开启构造RealData的线程。接收请求之后,会直接返回一个FutureData。
public class Client {
public Data<String> request(){
final FutureData futureData=new FutureData();
new Thread(()->{
RealData<String> realData=new RealData<>("hello,world");
futureData.setRealData(realData);
}).start();
return futureData;
}
}
主函数Main
public static void main(String[] args) throws InterruptedException {
Client client = new Client();
Data<String> request = client.request();
for(int i=0;i<5;i++){
System.out.println("处理其他事情中...");
TimeUnit.SECONDS.sleep(1);
}
System.out.println(request.getResult());
System.out.println("整个程序已结束");
}
以下是程序输出的结果:
JDK中的Future模式
Future接口就类似之前的FutureData是用来立即返回的对象,通过get()可以获取到真实的数据。
RunnableFuture继承了Runnable与Future接口,其中run方法用于线程构建真实的数据。
FutureTask是RunnableFuture的一个具体实现类,其内部有一个内部类Sync,Sync最终会调用Callble接口,完成实际数据的组装。
Callable接口调用call方法来返回需要构造的实际数据。
实际案例
实现一个RealData实现Callable接口。
public class RealData implements Callable<String> {
@Override
public String call() throws Exception {
for (int i=0;i<10;i++){//使用循环睡眠来代替耗时操作
System.out.println("正在构造数据...");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
return "hello,world";
}
}
Callable接口的call方法会构造真实的数据,并返回。
public static void main(String[] args) throws InterruptedException, ExecutionException {
FutureTask<String> future=new FutureTask<>(new RealData());
ExecutorService executorService = Executors.newFixedThreadPool(1);
//执行FutureTask相当于之前调用Client
//内部开启线程执行call方法
executorService.execute(future);
System.out.println("请求完毕");
for(int i=0;i<5;i++){
System.out.println("做其他业务逻辑中...");
TimeUnit.SECONDS.sleep(1);
}
System.out.println("数据="+future.get());
System.out.println("程序执行完毕");
}
其本质和之前提到的Future模式一样,下面来剖析一下源码。
FutureTask源码分析
FutureTask的构造方法
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
这个构造方法只做了两件事:将Callable对象保存到数据域,将状态置为New。
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW; // ensure visibility of callable
}
这个构造方法将Runnable对象和result合起来构造出一个Callable对象,并且将状态设置为New。
来详细看看Executors.callable()方法
public static <T> Callable<T> callable(Runnable task, T result) {
if (task == null)
throw new NullPointerException();
return new RunnableAdapter<T>(task, result);
}
它实际返回一个RunnableAdapter对象。
private static final class RunnableAdapter<T> implements Callable<T> {
private final Runnable task;
private final T result;
RunnableAdapter(Runnable task, T result) {
this.task = task;
this.result = result;
}
public T call() {
task.run();
return result;
}
public String toString() {
return super.toString() + "[Wrapped task = " + task + "]";
}
}
这是一个经典的适配器模式的实现,用于将Runnable适配成Callable。
task是执行的真正任务逻辑,result是预定义的返回结果。
// 只是执行Runnable,返回一个占位结果
Runnable task = () -> { System.out.println("Task done"); };
Callable<String> callable = new RunnableAdapter<>(task, "DONE");
// 执行后总是返回固定的"DONE",不关心对象状态变化
FutureTask的run
因为FutureTask本身是一个Runnable,因此它重写了run方法。
public void run() {
if (state != NEW ||
!RUNNER.compareAndSet(this, null, Thread.currentThread()))
return;
try {
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
result = c.call();
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex);
}
if (ran)
set(result);
}
} finally {
// runner must be non-null until state is settled to
// prevent concurrent calls to run()
runner = null;
// state must be re-read after nulling runner to prevent
// leaked interrupts
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
这段代码,首先会检查状态并确保唯一的线程执行,检查通过后,运行Callable对象执行实际的业务。最后将执行的结果通过cas设置到数据域的outcome中。
FutureTask的get
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING)
s = awaitDone(false, 0L);
return report(s);
}
如果当前的状态不为COMPLETING,则意味着数据还未准备好,于是陷入阻塞。直到数据准备好被唤醒。