并行设计模式(3):Future模式

54 阅读5分钟

什么是Future模式?

Future模式是多线程开发中一种经典的设计模式,其核心思想是异步调用。它通过将耗时操作主线程分离,让主线程不必等待结果返回即可继续执行其他任务,从而显著提升系统的响应速度资源利用率

同步调用 image.png 异步调用

image.png

实现了Future模式的客户端在拿到这个返回结果后,并不急于处理,⽽是调⽤了其他业务逻辑,充分利⽤了等待时间,这就是Future模式的核心所在。在完成了其他业务逻辑的处理后,再使⽤返回⽐较慢的Future数据。

Future模式的主要参与者包括

  1. Main:系统入口,调用Client发起请求,处理其他业务,最终通过Future获取真实结果
  2. Client:接收请求后,立即返回FutureData,同时启动独立线程异步构建RealData
  3. Data:结果返回接口,定义获取真实结果的方法(如getResult()
  4. FutureData:虚拟结果实现类,封装RealData的构建等待逻辑,是Future模式的核心代理
  5. RealData:真实结果实现类,构造过程耗时(如数据计算、IO操作),最终通过getResult()返回

Future模式的简单实现

核心接口Data

Data就是客户端希望获取的数据,在Future模式中,Data接口有两个重要的实现:

  1. RealData:最终获取的真实数据
  2. FutureData:用于提取RealData
public interface Data<T> {
    public T getResult();
}

实现类FutureData

FutureData实现了一个可以快速返回的RealData包装,它只是一个虚拟实现,因此很快就可以返回。当使用FutureDatagetResult方法的时候,如果实际的数据还没有准备好,就会被阻塞,等待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("整个程序已结束");
}

以下是程序输出的结果:

image.png

JDK中的Future模式

image.png Future接口就类似之前的FutureData是用来立即返回的对象,通过get()可以获取到真实的数据。

RunnableFuture继承了RunnableFuture接口,其中run方法用于线程构建真实的数据。

FutureTaskRunnableFuture的一个具体实现类,其内部有一个内部类SyncSync最终会调用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适配成Callabletask是执行的真正任务逻辑,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,则意味着数据还未准备好,于是陷入阻塞。直到数据准备好被唤醒。