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

0 阅读3分钟

什么是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("程序执行完毕");
}