`

浅谈Future

    博客分类:
  • Java
 
阅读更多

Future到底是什么东西?很多人都对这个东西感到特别奇怪(好吧,我承认,那个很多人就只是我自己而已),就我现在的理解,因为本人在并发这方面没有多少实践经验,所以只好就着一些资料和自己的理解给它下个定义,Future就是保存我们任务的完成信息,比如说,任务中会通过返回某些东西告诉别人它已经结束了,而Future中就保存了这种信息。利用Futu保存和得到任务的结果的用法如下: 

复制代码
      Future<String> future = threadPool.submit(new Callable<String>(){
           @Override
           public String call() throws Exception{
                Thread.sleep(3000);
                return "future";
           }
        try{
             System.out.println("waiting.....");
             System.out.println(future.get());
        }catch(InterruptedException e){
              e.printStackTrace();
        }catch(ExecutionException e){
              e.printStackTrace();
        }
复制代码

          注意到没?代码中的System.out.println(future.get())这一句,就是返回任务的结果,而任务的结果是保存在Future<String>中的,相信大家都有注意到,        

     Future<String> future = threadPool.submit(new Callable<String>(){
       @Override
        public String call() throws Exception{}
       }

 

         Callable相当于Runnable,所以,这里实现的是一个线程,但是与Runnable不同的是,它是具有返回值的,这个返回值就是我们想要任务返回的结果,比如说,我们想要任务返回的是一个提示信息,那么,返回值可以是String,然后在我们要实现的call()方法中return一句提示信息,接着只要使用Future类的get()方法,就可以从里面得到提示信息了,只要任务完成。所以,由此我们可以知道,java SE5比起以前来,在并发这方面做了更多的工作,它完善了我们的并发线程机制,使我们可以更好的根据任务的完成情况来进行与其他任务的协作,比如说,我们可以通过Future的返回值来决定是否终止任务,或者开启另一个任务。任务的终止可以使用Future的方法future.cancel(boolean),其中boolean为true或false,来决定是否终止,至于开启另一个任务,可以重新开启另一个线程,但是这里就马上有个问题浮现出来,就是当Futrue的结果返回来时,该任务有没有结束呢?因为这时一定已经执行完该任务的call()方法。是的,该任务已经结束了,只是我们没有取出它的返回结果而已。

        看到上面,相信大家一定都对Future的新特性产生非常浓厚的兴趣,非常想要将这个新玩意儿马上运用起来,但是,且慢,每次在遇到这种新东西的时候,我们都会有一个念头,那就是我们有必要使用吗?如果旧的东西已经足够用了,那为什么还要用多余的方法呢?是的,这种想法是对的,因为我们的程序设计原则都是能够尽量简单则尽量简单,但是Future是一个接口,一个泛型接口,适合各种返回值的情况,而且这个接口提供了很多有用的方法,再加上,我们永远无法知道我们的代码以后到底会变成怎么样子,是否需要添加新的功能等等,而这些,如果一开始使用的是旧的东西的话,添加新的东西,那么,我们就要对我们的代码进行修改,但是,我是这么认为的,就目前而言,Thread修改为Future并不是很难,所以,这方面倒是没有多大顾虑,熟悉啥就用啥,至少都要了解,因为我们在写代码时,更多时间里是在阅读别人的代码,如果别人使用的代码是使用以前的接口的话,而且这种情况是非常常见的,所以,我们必须要看得懂代码并且能够将其转化为我们的新接口,这些就需要我们能够对其有一定的了解,并且明白它们之间的联系和区别。所以,接下来就是介绍一下新接口的一些方法以便我们能够更好的使用新接口。

    A、boolean cancel(Boolean mayInterruptlfRunning):试图取消该Future里关联的Callable任务
    B、<?> get()  throws InterruptedException, ExecutionException :返回Callable任务里的call方法的返回值,调用该方法将导致线程阻塞,必须等到子线程结束才得到返回值,但是并不会妨碍其他任务的执行。线程被中断会抛出InterruptedException异常,任务执行中出现错误会抛出ExecutionException异常,如果任务被取消,还会抛出CancellationException 异常。
    C、<?> get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException :返回Callable任务里的call方法的返回值,该方法让程序最多阻塞timeout和unit指定的时间,如果经过指定时间后Callable任务依然没有返回值,将会抛出TimeoutException,其他与上面的get()方法用法一样。
    D、boolean isCancelled():如果在Callable任务正常完成前被取消,则返回true。
    E、boolean isDone():如果Callable任务已经完成,则返回true,而且这里要注意,如果任务正常终止、异常或取消,那么都将会返回true.
    关于Future的使用,其实重点并不在于我们对它的方法到底有多么熟悉,因为我们完全可以通过查找文档来了解它们的用法,那么我们需要掌握的是什么呢?就是通过利用Future,我们可以做到什么?它解决的主要问题到底是什么?这些都是有利于我们对它的掌握。很多人都喜欢研究接口的内部实现,是的,这个很重要,因为想要了解接口的实现,知道它的实现细节是非常重要的,只要知道它的实现细节,那么我上面的问题其实也就可以马上得到答案。但是我并不是什么大师级人物,所以像是这种枯燥的东西完全没有信心可以讲得通俗易懂,自己没有晕都不错了!所以,就只能通过一些用例及其代码来研究研究,如果是非要知道底细的读者,还请自己详细阅读一下相关文档。
       那么,进入正题,既然我们知道Future是一个接口,那么,就一定有一个具体的类的实现,那么这个具体的类是什么呢?就是FutureTask.FutureTask的作用非常大,甚至可以说是使用Future的灵魂,因为就是它用来包装Callable对象的,所以,很多方法都是通过它来实现的。
      代码如:
      
复制代码
ExecutorService executor = Executors.newSingleThreadExecutor();   
FutureTask<String> future =   
       new FutureTask<String>(new Callable<String>() {//使用Callable接口作为构造参数   
         public String call() {   
           //真正的任务在这里执行,这里的返回值类型为String,可以为任意类型   
       }});   
executor.execute(future);   
//在这里可以做别的任何事情   
try {   
    result = future.get(5000, TimeUnit.MILLISECONDS); //取得结果,同时设置超时执行时间为5秒。同样可以用future.get(),不设置执行超时时间取得结果   
} catch (InterruptedException e) {   
    futureTask.cancel(true);   
} catch (ExecutionException e) {   
    futureTask.cancel(true);   
} catch (TimeoutException e) {   
    futureTask.cancel(true);   
} finally {   
    executor.shutdown();   
}  
复制代码

      这里就是FutureTask的一般用法,它最大的好处就是我们可以将任务交给执行器后执行其他操作,然后再从里面得到任务的结果。这里必须要注意,只有FutureTask这种既实现Runnable又实现Callable才能够通过executor()递交给ExecutorService,而Future不行,只能通过submit(),因为executor()要求的参数是一个实现了Runnable的类。如果我们不想要直接构造Future对象,那么我们可以这样写:

      

复制代码
ExecutorService executor = Executors.newSingleThreadExecutor();   
FutureTask<String> future = executor.submit(   
   new Callable<String>() {//使用Callable接口作为构造参数   
       public String call() {   
      //真正的任务在这里执行,这里的返回值类型为String,可以为任意类型   
   }});   
//在这里可以做别的任何事情   
//同上面取得结果的代码  
复制代码

       这里是使用ExecutorService.submit方法来获得Future对象,submit方法既支持Callable接口类型,也支持Runnable接口作为参数,具有很大的灵活性,而且所有的submit()方法都会返回一个Future值,无论是Runnable还是Callable。上面两种方法哪种比较好?其实都一样,只是第二种的话,可以在定义FutureTask的同时就将FutureTask提交给Executor。个人的话,比较倾向于第二种,因为我们的代码如果在不影响阅读性的基础上能够越简单越好,哪怕是一句代码。

       Future中的call()方法相比run()方法更加强大,除了上面说的可以具有返回值外,相信大家在上面的代码中也可以看到,call()方法是可以声明异常的,这样,就能省去run()方法的异常处理。
   创建并启动有返回值的线程的步骤如下:
   一、创建Callable接口的实现类,并实现call方法,该call方法的返回值,并作为线程的执行体。
   二、创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call方法的返回值
   三、使用FutureTask对象作为Thread对象的target创建、并启动新线程
   四、调用FutureTask对象的方法来获得子线程执行结束后的返回值
    FutureTask还实现了Runnable接口,所以可以直接交给Executor执行,正如上面的代码所演示的。这种好处就非常明显,因为我们还是可以将我们的线程交给Executor来管理,不用学习新的线程管理机制。我们都知道,Executor框架的实现基本上是用Runnable,但是Runnable的能力还是相当有限,它不能返回一个值,也不能抛出异常,更重要的是,对于复杂费时的计算根本难以处理。为什么这么说呢?那是因为Runnable并没有方法可以支持任务超时。什么是任务超时?就是我们允许这个任务的执行要多久后才返回结果,如果这个任务在指定的时间内没有完成,那么就会抛出异常,那就是可以设置超时的get()方法。但是我们知道,Runnable中并没有与get()类似的方法,我们最多能做的,只是让线程睡眠。
      至此我们就可以知道,Future大概是怎样的东西。其实,它的名字含义就已经说明一切,Future,未来的意思,说明我们通过Future是为了应付未来的问题。什么是未来的问题,这个就是它所解决的问题,超时问题,以前,我嫩得到任务的结果是即时性的,但是现在可以有一个时间上的缓冲,可以在这段等待的时间内执行其他的动作,这样我们的并发设计就能更加灵活,而且还能更好的应付现实生活中的实际问题,因为很多问题都是不能马上就能解决并且返回结果的。这就是Future的长处,异步处理,但是Future又不同于一般的异步处理机制,它也可以选择同步,所以灵活性更大。这点怎么做到的呢?取决于它里面的方法。比如说,我们可以根据流程的需要决定是否需要等待(Future.isDone()),何时等待(Future.get()),等待多久(Future.get(timeout)),还可以根据它的返回值判断数据是否就绪而决定要不要借这个空档完成其它任务。 
       现实生活中的并发问题非常多而且要求又不尽相同,所以,自然,Future也有很多相应的衍生形态。下面就只是介绍几种常见的,因为能力有限,加上这方面的工作又几乎没有,所以,如果是想要更加详细的内容,还请查阅相关资料。
       1.Lazy Future
       与一般的Future不同,Lazy Future在创建之初不会主动开始准备引用的对象,而是等到请求对象时才开始相应的工作。因此,Lazy Future本身并不是为了实现并发,而是以节约不必要的运算资源为出发点,效果上与Lambda/Closure类似。例如设计某些API时,你可能需要返回一组信息,而其中某些信息的计算可能会耗费可观的资源。但调用者不一定都关心所有的这些信息,因此将那些需要耗费较多资源的对象以Lazy Future的形式提供,可以在调用者不需要用到特定的信息时节省资源。
另外Lazy Future也可以用于避免过早的获取或锁定资源而产生的不必要的互斥,因为并没有准备对象。
       2.Promise
       Promise可以看作是Future的一个特殊分支,常见的Future一般是由服务调用者直接触发异步处理流程,比如调用服务时立即触发处理或 Lazy Future的取值时触发处理。但Promise则用于显式表示那些异步流程并不直接由服务调用者触发的情景。例如Future接口的定时控制,其异步流程不是由调用者,而是由系统时钟触发,再比如淘宝的分布式订阅框架提供的Future式订阅接口,其等待数据的可用性不是由订阅者决定,而在于发布者何时发布或更新数据。因此,相对于标准的Future,Promise接口一般会多出一个set()或fulfill()接口。
        3.复用式的Future
        常规的Future是一次性的,也就是说当你获得了异步的处理结果后,Future对象本身就失去意义了。但经过特殊设计的Future也可以实现复用,这对于可多次变更的数据显得非常有用。例如前面提到的淘宝分布式订阅框架所提供的Future式接口,它允许多次调用waitNext()方法(相当于Future.get()),每次调用时是否阻塞取决于在上次调用后是否又有数据发布,如果尚无更新,则阻塞直到下一次的数据发布。这样设计的好处是,接口的使用者可以在其任何合适的时机,或者直接简单的在独立的线程中通过一个无限循环响应订阅数据的变化,同时还可兼顾其它定时任务,甚至同时等待多个Future。简化的例子如下:

      

复制代码
for (;;) {  
  schedule = getNextScheduledTaskTime();  
  while(schedule > now()) {  
    try {  
      data = subscription.waitNext(schedule - now());  
      processData(data);  
    } catch(Exception e) {...}  
  }  
  doScheduledTask();  
}  
复制代码
分享到:
评论

相关推荐

    浅谈FLASH动画设计论文.doc

    \n\n随着技术的进步,Flash逐渐发展出多个版本,从最初的Future Splash Animator到Flash 1.0,再到Flash 5.0,每个版本都带来了新的功能和改进。尤其是ActionScript的进化,让Flash的编程能力得到了显著提升,使其...

    浅谈Java多线程处理中Future的妙用(附源码)

    "浅谈Java多线程处理中Future的妙用" 在Java多线程处理中,Future是一个非常重要的概念,它可以帮助我们更好地处理并发任务。Future是一个未来对象,里面保存着线程处理结果,它像一个提货凭证,拿着它你可以随时去...

    浅谈在Java中使用Callable、Future进行并行编程

    从Java 1.5开始,引入了Callable和Future接口,为并行编程提供了一种更为高效和便捷的解决方案。 Callable接口是Java并发库中的一个关键接口,位于`java.util.concurrent`包下。Callable接口与Runnable接口类似,但...

    浅谈CPU技术的发展.pdf

    浅谈CPU技术的发展.pdf 在计算机发展的五十年中,CPU(Central Processing Unit,中央处理器)技术的发展经历了多个阶段。从早期的4位CPU到具有MMX技术的Pentium时代,CPU技术的发展对于计算机性能的提高起到了关键...

    RustChinaConf2020-14.赖智超-《Rust异步和并发浅谈》.pdf

    《Rust异步和并发浅谈》的主题演讲深入探讨了Rust编程语言在处理异步和并发场景中的机制。Rust是由Onchain的区块链架构师赖智超在Rust China Conf 2020上分享的,展示了Rust在公司项目中的应用,包括区块链Wasm JIT...

    浅谈未来计算机操作系统的发展方向.docx

    "浅谈未来计算机操作系统的发展方向" 随着科学技术的不断发展与创新,计算机操作系统作为计算机系统的基础正在不断发展和完善。操作系统作为管理计算机软硬件资源、控制程序运行、改善人机界面和为应用软件提供支持...

    浅谈户外广告.docx

    Future trends include technological innovation, interactivity, creativity, better integration with the environment, targeting specific audiences, market dominance, improved regulation, and ongoing ...

    浅谈JAVA中多线程的实现.zip

    Future&lt;?&gt; future = executor.submit(new Runnable() { public void run() { // 任务代码 } }); ``` 4. synchronized关键字:用于同步控制,保证多线程环境下对共享资源的访问有序性。它可以修饰方法或代码块,...

    浅谈移动互联网时代高校办公自动化.pdf

    例如,Herry Blodget的《The Future Of Mobile》以及Karl E. Wiegers的著作,都可能为系统开发提供理论依据和实践指导。参考文献中提供的数据、分析和案例研究等,可以帮助开发者更好地理解移动互联网技术的发展趋势...

    浅谈动词不定式的句法功能与用法.doc

    为了保持句子的平衡,有时会使用“it”作为形式主语,而真正的主语不定式则放在句尾,如:“It is difficult for Marty Fielding to know what the future holds.” 在这里,“for Marty Fielding to know”是真正...

    浅谈新能源汽车的下线调试与维修.pdf

    4. Challenges and Future DirectionsDespite the advantages, there are still challenges in integrating electronic diagnostic technology into new energy vehicle maintenance. These include the high cost ...

    浅谈Android中多线程切换的几种方法

    浅谈Android中多线程切换的几种方法 本篇文章主要介绍了Android中多线程切换的几种方法,包括Thread、ThreadPool、Runnable、Callable、Future、Condition、Handler等方法。多线程切换是Android开发中必现的场景,...

    浅谈Spring @Async异步线程池用法总结

    浅谈Spring @Async异步线程池用法总结 本文主要介绍了Spring @Async异步线程池的用法总结,包括异步线程池的接口类、@Async定义异步任务、Spring开启异步配置等内容。 1. 异步线程池的接口类 Spring提供了多种...

    浅谈Java内存模型之happens-before

    "浅谈Java内存模型之happens-before" Java内存模型是Java虚拟机中的一种机制,用于定义Java程序中线程之间的内存访问方式。在多线程环境下,线程之间的通信和数据共享是非常复杂的,需要有一些规则来保证数据的一致...

    浅谈Java线程并发知识点

    `FutureTask`是`Executor`框架的一部分,它代表一个异步计算的结果,可以通过`Callable`接口实现,其`Future.get()`方法可以等待任务完成并获取结果。 同步器,如`Semaphore`(信号量)、`CyclicBarrier`(屏障/关卡)...

    浅谈Java Fork/Join并行框架

    import java.util.concurrent.Future; import java.util.concurrent.RecursiveTask; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; /* Created by hujian06 on 2017/9/28....

    浅谈Python2、Python3相对路径、绝对路径导入方法

    from __future__ import absolute_import python2缺省会搜索上一级目录、上上级目录 解决方案: import platform pver=platform.python_version() pversion= int(pver.split('.')[0]) try: if pversion==2: print...

    浅谈java中异步多线程超时导致的服务异常

    3. **使用Future和Timeout**:如代码示例所示,可以利用`Future.get()`方法的超时参数,当线程执行超过设定时间时,可以选择抛出异常或者返回默认值,而不是阻塞其他线程。 4. **线程同步与通信**:使用`...

    浅谈Django中的数据库模型类-models.py(一对一的关系)

    from __future__ import unicode_literals from django.db import models # Create your models here. # 一对一关系:数据库中两个表中数据的对应关系 # 一个账户对应着一个联系人,而一个联系人有一个账户 # 一对一...

Global site tag (gtag.js) - Google Analytics