Future理解

日期:2018-09-28       浏览:601

一 Future的使用

用户发起一个请求到服务端,服务端要进行大量的业务操作,例如操作数据库、操作缓存、外部系统之间的网络接口调用等。如果这个用户请求业务很复杂,可能就要等待的比较久服务器才能够返回处理结果。
下面我们写一个例子测试一下:
public static void main(String[] args) throws InterruptedException {
    long startTime = System.currentTimeMillis();

    System.out.println("+++ 同步发起外部网络请求开始");
    Thread.sleep(3000);
    System.out.println("+++ 同步发起外部网络请求结束");
    System.out.println("--- 操作缓存开始");
    Thread.sleep(1000);
    System.out.println("--- 操作缓存结束");
    System.out.println("=== 操作数据库开始");
    Thread.sleep(2000);
    System.out.println("=== 操作数据库结束");

    System.out.println("~~~~ 整个接口耗时(ms)=" + (System.currentTimeMillis() - startTime));
}
执行结果:
+++ 同步发起外部网络请求开始
+++ 同步发起外部网络请求结束
--- 操作缓存开始
--- 操作缓存结束
=== 操作数据库开始
=== 操作数据库结束
~~~~ 整个接口耗时(ms)=6014
以上我们写的同步的测试用例接口耗时为6014ms。
业务处理的时长=外部网络请求时长(3s)+操作缓存时长(1s)+操作数据库时长(2s);
我们再写一个网络请求异步的例子:
public static void main(String[] args) throws InterruptedException {
    long startTime = System.currentTimeMillis();

    System.out.println("+++ 异步发起外部网络请求开始");
    ExecutorService es = Executors.newFixedThreadPool(1);
    FutureTask<String> future = new FutureTask<>(() -> {
        Thread.sleep(3000);
        return "ok";
    });
    es.submit(future);
    System.out.println("+++ 异步发起外部网络请求结束");
    System.out.println("--- 操作缓存开始");
    Thread.sleep(1000);
    System.out.println("--- 操作缓存结束");
    System.out.println("=== 操作数据库开始");
    Thread.sleep(2000);
    System.out.println("=== 操作数据库结束");

    System.out.println("~~~~ 整个接口耗时(ms)=" + (System.currentTimeMillis() - startTime));
}
执行结果:
+++ 异步发起外部网络请求开始
+++ 异步发起外部网络请求结束
--- 操作缓存开始
--- 操作缓存结束
=== 操作数据库开始
=== 操作数据库结束
~~~~ 整个接口耗时(ms)=3401
可以看到我们将外部网络请求改为异步后,整个接口的请求耗时变为了3401ms。外部网络请求和缓存加数据库操作是并行执行的,以前一个人的事现在两个人一起做,耗时也就缩短了。
业务处理的时长=外部网络请求时长(3s) || (操作缓存时长(1s)+操作数据库时长(2s));

二 实现自己的Future

以上例子的异步请求是JDK自带的 FutureTask。具体是怎么实现的呢?接下来我们也自己实现一个基本的MyFuture。
MyFure具体实现类:
public class MyFuture<T> implements Callable {

    private Supplier<T> supplier;
    private volatile boolean isRead = false;
    private T data;

    public MyFuture(Supplier<T> supplier) {
        this.supplier = supplier;
    }

    /**
     * 发起异步请求
     * @return
     * @throws Exception
     */
    @Override
    public T call() {
        try {
            // supplier.get() 为调用方具体的业务实现,设置请求返回结果
            data = supplier.get();
            isRead = true;
        }  finally {
            // 通知等待的线程
            synchronized(this) {
                notifyAll();
            }
        }

        return data;
    }

    /**
     * 获取请求返回结果,结果未返回则等待
     * @return
     */
    public synchronized T get() {
        while (!isRead) {
            try {
                wait();
            } catch (Exception e) {}
        }
        return data;
    }
}
测试程序:
public static void main(String[] args) throws InterruptedException {
    long startTime = System.currentTimeMillis();

    System.out.println("+++ 异步发起外部网络请求开始");
    ExecutorService es = Executors.newFixedThreadPool(1);
    MyFuture<String> future = new MyFuture<>(() -> {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "ok";
    });
    es.submit(future);
    System.out.println("+++ 异步发起外部网络请求结束");
    System.out.println("--- 操作缓存开始");
    Thread.sleep(1000);
    System.out.println("--- 操作缓存结束");
    System.out.println("=== 操作数据库开始");
    Thread.sleep(2000);
    System.out.println("=== 操作数据库结束");

    System.out.println("~~~~ 整个接口耗时(ms)=" + (System.currentTimeMillis() - startTime));
}
测试结果:
+++ 异步发起外部网络请求开始
+++ 异步发起外部网络请求结束
--- 操作缓存开始
--- 操作缓存结束
=== 操作数据库开始
=== 操作数据库结束
~~~~ 整个接口耗时(ms)=3299
可以看到 Future 就是创建了一个新的线程去做异步操作,最后再将结果返回。
除了基本的功能外,JDK 还为 Future 接口提供了一些简单的控制功能:
boolean cancel(boolean mayInterruptIfRunning);  // 取消任务
boolean isCancelled();                          // 是否已经取消
boolean isDone();                               // 是否已经完成
V get();                                        // 取得返回结果
V get(long timeout, TimeUnit unit);             // 取得返回结果,可以设置超时时间

三 JDK中的Future设计

首先我们看一下 JDK Future 类图:
JDK Future 类图
JDK Future 类图
由上图我们可以看到,我们可以通过 Future 接口获得真实的数据。RunnableFuture 继承了 Future 和 Runnable 两个接口,其中 run() 方法用于构造真实的数据。它有一个具体的实现 FutureTask 类。FutureTask 有一个内部类 Sync,一些实质性的工作会委托 Sync 类实现。而 Sync 类最终会调用 Callable 接口,完成实际数据的组装工作。
扫码关注有惊喜

(转载本站文章请注明作者和出处 qbian)

暂无评论

Copyright 2016 qbian. All Rights Reserved.

文章目录