JAVA Future类的使用详解

前言

在高性能编程中,并发编程已经成为了极为重要的一部分。在单核CPU性能已经趋于极限时,我们只能通过多核来进一步提升系统的性能,因此就催生了并发编程。

由于并发编程比串行编程更困难,也更容易出错,因此,我们就更需要借鉴一些前人优秀的,成熟的设计模式,使得我们的设计更加健壮,更加完美。

而Future模式,正是其中使用最为广泛,也是极为重要的一种设计模式。今天就跟阿丙了解一手Future模式!

生活中的Future模式

为了更快的了解Future模式,我们先来看一个生活中的例子。

场景1:

午饭时间到了,同学们要去吃饭了,小王下楼,走了20分钟,来到了肯德基,点餐,排队,吃饭一共花了20分钟,又花了20分钟走回公司继续工作,合计1小时。

场景2

午饭时间到了,同学们要去吃饭了,小王点了个肯德基外卖,很快,它就拿到了一个订单(虽然订单不能当饭吃,但是有了订单,还怕吃不上饭嘛)。接着小王可以继续干活,30分钟后,外卖到了,接着小王花了10分钟吃饭,接着又可以继续工作了,成功的卷到了隔壁的小汪。

很明显,在这2个场景中,小王的工作时间更加紧凑,特别是那些排队的时间都可以让外卖员去干,因此可以更加专注于自己的本职工作。聪明的你应该也已经体会到了,场景1就是典型的函数同步调用,而场景2是典型的异步调用。

而场景2的异步调用,还有一个特点,就是它拥有一个返回值,这个返回值就是我们的订单。这个订单很重要,凭借着这个订单,我们才能够取得当前这个调用所对应的结果。

这里的订单就如同Future模式中的Future,这是一个合约,一份承诺。虽然订单不能吃,但是手握订单,不怕没吃的,虽然Future不是我们想要的结果,但是拿着Future就能在将来得到我们想要的结果。

因此,Future模式很好的解决了那些需要返回值的异步调用。

Future模式中的主要角色

一个典型的Future模式由以下几个部分组成:

  • Main:系统启动,调用Client发出请求
  • Client:返回Data对象,立即返回FutureData,并开启ClientThread线程装配RealData
  • Data:返回数据的接口
  • FutureData:Future数据,构造很快,但是是一个虚拟的数据,需要装配RealData,好比一个订单
  • RealData:真实数据,其构造是比较慢的,好比上面例子中的肯德基午餐。

它们之间的相互关系如下图:

其中,值得注意是Data,RealData和FutureData。这是一组典型的代理模式,Data接口表示对外数据,RealData表示真实的数据,就好比午餐,获得它的成本比较高,需要很多时间;相对的FutureData作为RealData的代理,类似于一个订单/契约,通过FutureData,可以在将来获得RealData。

因此,Future模式本质上是代理模式的一种实际应用。

实现一个简单的Future模式

根据上面的设计,让我们来实现一个简单的代理模式吧!

首先是Data接口,代表数据:

public interface Data {
    public String getResult ();
}

接着是FutureData,也是整个Future模式的核心:

public class FutureData implements Data {
    // 内部需要维护RealData
    protected RealData realdata = null;
    protected boolean isReady = false;
    public synchronized void setRealData(RealData realdata) {
        if (isReady) {
            return;
        }
        this.realdata = realdata;
        isReady = true;
        //RealData已经被注入,通知getResult()
        notifyAll();
    }
    //会等待RealData构造完成
    public synchronized String getResult() {
        while (!isReady) {
            try {
                //一直等待,直到RealData被注入
                wait();
            } catch (InterruptedException e) {
            }
        }
        //真正需要的数据从RealData获取
        return realdata.result;
    }
}

下面是RealData:

public class RealData implements Data {
    protected final String result;
    public RealData(String para) {
        StringBuffer sb=new StringBuffer();
        //假设这里很慢很慢,构造RealData不是一个容易的事
        result =sb.toString();
    }
    public String getResult() {
        return result;
    }
}

然后从Client得到Data:

public class Client {
    //这是一个异步方法,返回的Data接口是一个Future
    public Data request(final String queryStr) {
        final FutureData future = new FutureData();
        new Thread() {
            public void run() {
                // RealData的构建很慢,所以在单独的线程中进行
                RealData realdata = new RealData(queryStr);
                //setRealData()的时候会notify()等待在这个future上的对象
                future.setRealData(realdata);
            }
        }.start();
        // FutureData会被立即返回,不会等待RealData被构造完
        return future;
    }
}

最后一个Main函数,把所有一切都串起来:

public static void main(String[] args) {
    Client client = new Client();
    //这里会立即返回,因为得到的是FutureData而不是RealData
    Data data = client.request("name");
    System.out.println("请求完毕");
    try {
        //这里可以用一个sleep代替了对其他业务逻辑的处理
        //在处理这些业务逻辑的过程中,RealData被创建,从而充分利用了等待时间
        Thread.sleep(2000);
    } catch (InterruptedException e) {
    }
    //使用真实的数据,如果到这里数据还没有准备好,getResult()会等待数据准备完,再返回
    System.out.println("数据 = " + data.getResult());
}

这是一个最简单的Future模式的实现,虽然简单,但是已经包含了Future模式中最精髓的部分。对大家理解JDK内部的Future对象,有着非常重要的作用。

Java中的Future模式

Future模式是如此常用,在JDK内部已经有了比较全面的实现和支持。下面,让我们一起看看JDK内部的Future实现:

首先,JDK内部有一个Future接口,这就是类似前面提到的订单,当然了,作为一个完整的商业化产品,这里的Future的功能更加丰富了,除了get()方法来获得真实数据以外,还提供一组辅助方法,比如:

  • cancel():如果等太久,你可以直接取消这个任务
  • isCancelled():任务是不是已经取消了
  • isDone():任务是不是已经完成了
  • get():有2个get()方法,不带参数的表示无穷等待,或者你可以只等待给定时间

下面代码演示了这个Future的使用方法:

        //异步操作 可以用一个线程池
        ExecutorService executor = Executors.newFixedThreadPool(1);
        //执行FutureTask,相当于上例中的 client.request("name") 发送请求
        //在这里开启线程进行RealData的call()执行
        Future<String> future = executor.submit(new RealData("name"));
        System.out.println("请求完毕,数据准备中");
        try {
            //这里依然可以做额外的数据操作,这里使用sleep代替其他业务逻辑的处理
            Thread.sleep(2000);
        } catch (InterruptedException e) {
        }
        //如果此时call()方法没有执行完成,则依然会等待
        System.out.println("数据 = " + future.get());

整个使用过程非常简单,下面我们来分析一下executor.submit()里面究竟发生了什么:

    public <T> Future<T> submit(Callable<T> task) {
        if (task == null) throw new NullPointerException();
        // 根据Callable对象,创建一个RunnableFuture,这里其实就是FutureTask
        RunnableFuture<T> ftask = newTaskFor(task);
        //将ftask推送到线程池
        //在新线程中执行的,就是run()方法,在下面的代码中有给出
        execute(ftask);
        //返回这个Future,将来通过这个Future就可以得到执行的结果
        return ftask;
    }
    protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
        return new FutureTask<T>(callable);
    }

最关键的部分在下面,FutureTask作为一个线程单独执行时,会将结果保存到outcome中,并设置任务的状态,下面是FutureTask的run()方法:

从FutureTask中获得结果的实现如下:

    public V get() throws InterruptedException, ExecutionException {
        int s = state;
        //如果没有完成,就等待,回到用park()方法阻塞线程
        //同时,所有等待线程会在FutureTask的waiters字段中排队等待
        if (s <= COMPLETING)
            s = awaitDone(false, 0L);
        return report(s);
    }
    private V report(int s) throws ExecutionException {
        //outcome里保存的就是最终的计算结果
        Object x = outcome;
        if (s == NORMAL)
            //正常完成,就返回outcome
            return (V)x;
        //如果没有正常完成, 比如被用户取消了,或者有异常了,就抛出异常
        if (s >= CANCELLED)
            throw new CancellationException();
        throw new ExecutionException((Throwable)x);
    }

Future模式的高阶版本—— CompletableFuture

Future模式虽然好用,但也有一个问题,那就是将任务提交给线程后,调用线程并不知道这个任务什么时候执行完,如果执行调用get()方法或者isDone()方法判断,可能会进行不必要的等待,那么系统的吞吐量很难提高。

为了解决这个问题,JDK对Future模式又进行了加强,创建了一个CompletableFuture,它可以理解为Future模式的升级版本,它最大的作用是提供了一个回调机制,可以在任务完成后,自动回调一些后续的处理,这样,整个程序可以把“结果等待”完全给移除了。

下面来看一个简单的例子:

在这个例子中,首先以getPrice()为基础创建一个异步调用,接着,使用thenAccept()方法,设置了一个后续的操作,也就是当getPrice()执行完成后的后续处理。

不难看到,CompletableFuture比一般的Future更具有实用性,因为它可以在Future执行成功后,自动回调进行下一步的操作,因此整个程序不会有任何阻塞的地方(也就是说你不用去到处等待Future的执行,而是让Future执行成功后,自动来告诉你)。

以上面的代码为例,CompletableFuture之所有会有那么神奇的功能,完全得益于AsyncSupply类(由上述代码中的supplyAsync()方法创建)。

AsyncSupply在执行时,如下所示:

        public void run() {
            CompletableFuture<T> d; Supplier<T> f;
            if ((d = dep) != null && (f = fn) != null) {
                dep = null; fn = null;
                if (d.result == null) {
                    try {
                        //这里就是你要执行的异步方法
                        //结果会被保存下来,放到d.result字段中
                        d.completeValue(f.get());
                    } catch (Throwable ex) {
                        d.completeThrowable(ex);
                    }
                }
                //执行成功了,进行后续处理,在这个后续处理中,就会调用thenAccept()中的消费者
                //这里就相当于Future完成后的通知
                d.postComplete();
            }
        }

继续看d.postComplete(),这里会调用后续一系列操作

   final void postComplete() {
                //省略部分代码,重点在tryFire()里
                //在tryFire()里,真正触发了后续的调用,也就是thenAccept()中的部分
                f = (d = h.tryFire(NESTED)) == null ? this : d;
            }
        }
    }

絮叨

今天,我们主要介绍Future模式,我们从一个最简单的Future模式开始,逐步深入,先后介绍了JDK内部的Future模式实现,以及对Future模式的进化版本CompletableFuture做了简单的介绍。对

于多线程开发而言,Future模式的应用极其广泛,可以说这个模式已经成为了异步开发的基础设施。

以上就是JAVA Future类的使用详解的详细内容,更多关于JAVA Future类的使用的资料请关注我们其它相关文章!

(0)

相关推荐

  • java通过Callable和Future来接收线程池的执行结果

    在Java的线程执行中,不管是直接继承Thread的方式,还是实现Runnable接口的方式,都不会获取到线程执行的返回结果.这样如果线程在执行过程中出现了错误,那么主线程也不会感知到.即使打印了日志,也不能立即抛出异常.事后查看日志才能发现出现了bug.而且到那时发生问题的代码点距离真正的问题点可能会相差很远.如果在线程池执行的过程中出现了bug能及时地抛出异常,那么这将会是一个很好的实现.解决上述问题的办法是使用Callable接口,其可以获取到线程的返回结果,通过Future的get方法来

  • 了解JAVA Future类

    1. Future的应用场景 在并发编程中,我们经常用到非阻塞的模型,在之前的多线程的三种实现中,不管是继承thread类还是实现runnable接口,都无法保证获取到之前的执行结果.通过实现Callback接口,并用Future可以来接收多线程的执行结果. Future表示一个可能还没有完成的异步任务的结果,针对这个结果可以添加Callback以便在任务执行成功或失败后作出相应的操作. 举个例子:比如去吃早点时,点了包子和凉菜,包子需要等3分钟,凉菜只需1分钟,如果是串行的一个执行,在吃上早点

  • Java FutureTask类使用案例解析

    FutureTask一个可取消的异步计算,FutureTask 实现了Future的基本方法,提空 start cancel 操作,可以查询计算是否已经完成,并且可以获取计算的结果.结果只可以在计算完成之后获取,get方法会阻塞当计算没有完成的时候,一旦计算已经完成,那么计算就不能再次启动或是取消. 一个FutureTask 可以用来包装一个 Callable 或是一个runnable对象.因为FurtureTask实现了Runnable方法,所以一个 FutureTask可以提交(submit

  • 简谈java并发FutureTask的实现

    概述 在使用java多线程解决问题的时候,为了提高效率,我们常常会异步处理一些计算任务并在最后异步的获取计算结果,这个过程的实现离不开Future接口及其实现类FutureTask.FutureTask类实现了Runnable, Future接口,接下来我会通过源码对该类的实现进行详解. 使用 我们先看下FutureTask中的主要方法如下,可以看出FutureTask实现了任务及异步结果的集合功能.看到这块的方法,大家肯定会有疑问,Runnable任务的run方法返回空,FutureTask如

  • Java多线程Callable和Future接口区别

    Runnable是执行工作的独立任务,但是不返回任何值.如果我们希望任务完成之后有返回值,可以实现Callable接口.在JavaSE5中引入的Callable是一个具有类型参数的范型,他的类型参数方法表示为方法call()而不是run()中返回的值,并且必须使用ExecutorService.submint()方法进行调用. 代码如下 import java.util.concurrent.Callable; import java.util.concurrent.ExecutionExcep

  • Java使用Callable和Future创建线程操作示例

    本文实例讲述了Java使用Callable和Future创建线程操作.分享给大家供大家参考,具体如下: 一 点睛 从Java 5开始,Java提供了Callable接口,该接口是Runnable接口的增强版,Callable接口提供了一个call()方法,可以看作是线程的执行体,但call()方法比run()方法更强大. call()方法可以有返回值. call()方法可以声明抛出异常. 创建并启动线程的步骤如下: 1 创建Callable接口的实现类,并实现call()方法,该call()方法

  • java多线程Future和Callable类示例分享

    一,描写叙述 ​在多线程下编程的时候.大家可能会遇到一种需求,就是我想在我开启的线程都结束时,同一时候获取每一个线程中返回的数据然后再做统一处理,在这种需求下,Future与Callable的组合就派上了非常大的用场. 也有人会说,我能够使用同步来完毕这个需求啊,普通情况下确实能够.可是在一种特殊情况下就不行了: ​想象,你开启了多个线程同步计算一些数据,可是大家都知道,线程是会争用资源的,也就是说.你开启多个线程来同步计算数据时.事实上线程之间的计算顺序是不可空的,当然除非你非非常大周折去处理

  • Java中Future、FutureTask原理以及与线程池的搭配使用

    Java中的Future和Future通常和线程池搭配使用,用来获取线程池返回执行后的返回值.我们假设通过Executors工厂方法构建一个线程池es ,es要执行某个任务有两种方式,一种是执行 es.execute(runnable) ,这种情况是没有返回值的: 另外一种情况是执行 es.submit(runnale)或者 es.submit(callable) ,这种情况会返回一个Future的对象,然后调用Future的get()来获取返回值. Future public interfac

  • java自定义任务类定时执行任务示例 callable和future接口使用方法

    Callable 和 Future接口Callable是类似于Runnable的接口,实现Callable接口的类和实现Runnable的类都是可被其它线程执行的任务. Callable和Runnable有几点不同: (1)Callable规定的方法是call(),而Runnable规定的方法是run().(2)Callable的任务执行后可返回值,而Runnable的任务是不能返回值的. (3)call()方法可抛出异常,而run()方法是不能抛出异常的.(4)运行Callable任务可拿到一

  • JAVA Future类的使用详解

    前言 在高性能编程中,并发编程已经成为了极为重要的一部分.在单核CPU性能已经趋于极限时,我们只能通过多核来进一步提升系统的性能,因此就催生了并发编程. 由于并发编程比串行编程更困难,也更容易出错,因此,我们就更需要借鉴一些前人优秀的,成熟的设计模式,使得我们的设计更加健壮,更加完美. 而Future模式,正是其中使用最为广泛,也是极为重要的一种设计模式.今天就跟阿丙了解一手Future模式! 生活中的Future模式 为了更快的了解Future模式,我们先来看一个生活中的例子. 场景1: 午饭

  • java Future 接口使用方法详解

    java Future 接口使用方法详解 在Java中,如果需要设定代码执行的最长时间,即超时,可以用Java线程池ExecutorService类配合Future接口来实现. Future接口是Java标准API的一部分,在java.util.concurrent包中.Future接口是Java线程Future模式的实现,可以来进行异步计算. Future模式可以这样来描述:我有一个任务,提交给了Future,Future替我完成这个任务.期间我自己可以去做任何想做的事情.一段时间之后,我就便

  • Java自定义异常类的实例详解

    Java自定义异常类的实例详解 为什么要自己编写异常类?假如jdk里面没有提供的异常,我们就要自己写.我们常用的类ArithmeticException,NullPointerException,NegativeArraySizeException,ArrayIndexoutofBoundsException,SecurityException这些类,都是继续着RuntimeException这个父类,而这个父类还有一个父类是Exception.那么我们自己写异常类的时候,也是继续Excepti

  • C++/java 继承类的多态详解及实例代码

    C++/java 继承类的多态详解 学过C++和Java的人都知道,他们二者由于都可以进行面向对象编程,而面向对象编程的三大特性就是封装.继承.多态,所有今天我们就来简单了解一下C++和Java在多态这方面的不同. 首先我们各看一个案例. C++ //测试继承与多态 class Animal { public: char name[128]; char behavior[128]; void outPut() { cout << "Animal" << endl

  • java Wrapper类基本用法详解

    在封装中有一种特殊的类,能够把基本的数据类型进行转换来方便实际的使用.我们在之前提到的一些数据类型,最明显的特征是所有字母为小写状态,那么经过Wrapper的包装后,首字母就变成了大写.下面我们就这种特殊的封装类Wrapper的概念.转换图解.模式以及实例带来分享. 1.概念 wrapper类是在Java中创建对象引用类型的原始类型的方式.我们可以说,通过提供wrapper类,使Java在面向对象技术的本质上摆脱了困境,即使是原类型没有被保存. 2.转换图解 该类主要用于基本数据类型和字符串之间

  • Java Collections类操作集合详解

    Collections 类是 Java 提供的一个操作 Set.List 和 Map 等集合的工具类.Collections 类提供了许多操作集合的静态方法,借助这些静态方法可以实现集合元素的排序.查找替换和复制等操作.下面介绍 Collections 类中操作集合的常用方法. 1) 排序(Sort)     使用sort方法可以根据元素的自然顺序,对指定列表进行排序.列表中的所有元素都必须实现 Comparable 接口.或此列表内的所有元素都必须是使用指定比较器可相互比较的   Collec

  • Java Calendar类使用案例详解

    在实际项目当中,我们经常会涉及到对时间的处理,例如登陆网站,我们会看到网站首页显示XXX,欢迎您!今天是XXXX年....某些网站会记录下用户登陆的时间,比如银行的一些网站,对于这些经常需要处理的问题,Java中提供了Calendar这个专门用于对日期进行操作的类,那么这个类有什么特殊的地方呢,首先我们来看Calendar的声明 public abstract class Calendar extends Objectimplements Serializable, Cloneable, Com

  • Java工具类DateUtils实例详解

    本文实例为大家分享了Java工具类DateUtils的具体代码,供大家参考,具体内容如下 import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; /** * 描述:公共日期工具类 */ public class DateUtils { public static String DATE_FORMAT = "yyyy-M

  • Java嵌套类和内部类详解

    一.什么是嵌套类及内部类? 可以在一个类的内部定义另一个类,这种类称为嵌套类(nested classes),它有两种类型: 静态嵌套类和非静态嵌套类.静态嵌套类使用很少,最重要的是非静态嵌套类,也即是被称作为 内部类(inner).嵌套类从JDK1.1开始引入.其中inner类又可分为三种: 其一.在一个类(外部类)中直接定义的内部类:     其二.在一个方法(外部类的方法)中定义的内部类;     其三.匿名内部类. 下面,我将说明这几种嵌套类的使用及注意事项. 二.静态嵌套类 如下所示代

  • 基于java涉及父子类的异常详解

    java中的异常涉及到父子类的问题,可以归纳为一句话:子类的构造函数抛出的异常必须包含父类的异常,子类的方法可以选择抛出"范围小于等于"父类的异常或不抛出异常. 1. 为什么构造函数必须抛出包含父类的异常? 在<thingking in java>中有这么一段话: 异常限制:当覆盖方法时,只能抛出在基类方法的异常说明中列出的那些异常 异常限制对构造器不起作用,你会发现StormyInning的构造器可以抛出任何异常,而不必理会基类构造函数所抛出的异常.然而因为必须构造函数必

随机推荐