zoukankan      html  css  js  c++  java
  • 关于Future

    一.Future是什么

    Future表示一个可能还没有完成的异步任务的结果,针对这个结果可以添加Callback以便在任务执行成功或失败后作出相应的操作。

    二.Future能做什么

    Future在处理异步调用和并发处理时非常有用。

    创建线程的2种方式,一种是直接继承Thread,另外一种就是实现Runnable接口。

    这2种方式都有一个缺陷就是:在执行完任务之后无法获取执行结果。

    如果需要获取执行结果,就必须通过共享变量或者使用线程通信的方式来达到效果,这样使用起来就比较麻烦。

    而自从Java 1.5开始,就提供了Callable和Future,通过它们可以在任务执行完毕之后得到任务执行结果。

    Future接口是长时间运行方法异步处理的理想选择。这使我们能够在等待Future封装的任务完成时执行一些其他事情。

    利用Future的异步性质的操作示例如下:

    (1)计算密集型过程(数学和科学计算)。

    (2)操纵大数据结构(大数据)。

    (3)远程方法调用(下载文件,抓取HTML,Web服务)。

    三.Future原理

    Future 是一 Java 1.5 带进来的产物。Future 本身不代表着多线程,而是代表着需异步计算的结果,将来的一个期待,至于后来真正的结果不可知。

    在此之前想要获得一个 Runnable 在其他线程中的计算结果颇费波折,有了 Future 加之它身后的 Callable 一切就变得简单了。

    对比一下 Java 1.5 前后的下面几个概念:

    (1)Callable 相当于之前的 Runnable, 只是 Callable 是有返回值的;

    (2)ExecuteService.submit(callable): Future 就类似于之前的 Thread(runnable)

    只是前者 submit 后立即执行,通过 get() 获得结果,后者用 start() 方法启动,runnable 是没有结果的。

    如果不想关心 Future 的结果也能 ExecuteService.submit(runnable),只有 callable 被提交(到线程池) 后返回的 Future 才可能会有结果, 所以下面的代码永远等不到结果。

    Future接口定义了主要的5个接口方法,有RunnableFuture和SchedualFuture继承这个接口,以及CompleteFuture和ForkJoinTask继承这个接口。

    1、RunnableFuture

    这个接口同时继承Future接口和Runnable接口,在成功执行run()方法后,可以通过Future访问执行结果。

    这个接口都实现类是FutureTask,一个可取消的异步计算,这个类提供了Future的基本实现,后面我们的demo也是用这个类实现,

    它实现了启动和取消一个计算,查询这个计算是否已完成,恢复计算结果。计算的结果只能在计算已经完成的情况下恢复。

    如果计算没有完成,get方法会阻塞,一旦计算完成,这个计算将不能被重启和取消,除非调用runAndReset方法。

    FutureTask能用来包装一个Callable或Runnable对象,因为它实现了Runnable接口,而且它能被传递到Executor进行执行。为了提供单例类,这个类在创建自定义的工作类时提供了protected构造函数。

    2、SchedualFuture

    这个接口表示一个延时的行为可以被取消。通常一个安排好的future是定时任务SchedualedExecutorService的结果。

    3、CompleteFuture

    一个Future类是显示的完成,而且能被用作一个完成等级,通过它的完成触发支持的依赖函数和行为。当两个或多个线程要执行完成或取消操作时,只有一个能够成功。

    4、ForkJoinTask

    基于任务的抽象类,可以通过ForkJoinPool来执行。一个ForkJoinTask是类似于线程实体,但是相对于线程实体是轻量级的。

    大量的任务和子任务会被ForkJoinPool池中的真实线程挂起来,以某些使用限制为代价。

    ForkJoinTask的主要特征是它通常会产生新的子任务,作为完成其主要任务所需的工作的一部分。它通过调用fork()生成新任务,并使用join()收集所有结果,从而得到类的名称。

    有两个实现ForkJoinTask的抽象类:RecursiveTask,它在完成时返回一个值,而RecursiveAction则不返回任何内容。顾名思义,这些类将用于递归任务,例如文件系统导航或复杂的数学计算。

    5、总结

    单使用Future是不方便的,其主要原因有:一方面没有提供方法去判断第一个完成的任务;

    另一方面是 Future没有提供Callback机制,只能通过阻塞的get方法去获取结果。

    针对第一个问题,JAVA引入了CompletionService接口。

    (1)CompletionService整合了Executor和BlockingQueue的功能。CompletionService维护一个保存Future对象的BlockingQueue。

    只有当这个Future对象状态是结束的时候,才会加入到这个Queue中。这样就确保执行时间较短的任务率先被存入队列中。

    与Future流程的不同主要是: 1. callable任务提交后,exexute方法执行的是封装成QueueingFuture的任务对象。

    QueueingFuture是FutureTask的子类,重写了done方法,在task执行完成之后将当前future添加到阻塞队列completionQueue。

    (2)CompletableFuture是JDK1.8新增的的类,提供了非常强大的Future的扩展功能。可以对多个异步处理进行编排,实现更复杂的异步处理。

    它能够将回调放到与任务不同的线程中执行,也能将回调作为继续执行的同步函数,在与任务相同的线程中执行。大家可以参考CompletableFuture的API了解它提供的功能。

    在实际项目中,需要根据具体的业务场景选择合适的Future工具类来实现异步编程。如果项目中使用Java 8,推荐使用CompletableFuture类,它提供了更多的异步控制。

    如果使用之前版本,可以使用Guava等框架提供的Future工具类。

     

    四.Future使用

    需求场景:

    等早餐过程中,包子需要3秒,凉菜需要1秒,普通的多线程需要四秒才能完成。

    先等凉菜,再等包子,因为等凉菜时,普通多线程启动start()方法,执行run()中具体方法时,没有返回结果,所以如果要等有返回结果,必须是要1秒结束后才知道结果。

    普通多线程:

    public class BumThread extends Thread{
        
        @Override
        public void run() {
            try {
                Thread.sleep(1000*3);
                System.out.println("包子准备完毕");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
     
    }
    public class ColdDishThread extends Thread{
        
        @Override
        public void run() {
            try {
                Thread.sleep(1000);
                System.out.println("凉菜准备完毕");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
     
    }
    public static void main(String[] args) throws InterruptedException {
            long start = System.currentTimeMillis();
            
            // 等凉菜 -- 必须要等待返回的结果,所以要调用join方法
            Thread t1 = new ColdDishThread();
            t1.start();
            t1.join();
            
            // 等包子 -- 必须要等待返回的结果,所以要调用join方法
            Thread t2 = new BumThread();
            t2.start();
            t2.join();
            
            long end = System.currentTimeMillis();
            System.out.println("准备完毕时间:"+(end-start));
        }

    采用Future模式:

    public static void main(String[] args) throws InterruptedException, ExecutionException {
    long start = System.currentTimeMillis(); // 等凉菜 Callable ca1 = new Callable(){ @Override public String call() throws Exception { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } return "凉菜准备完毕"; } }; FutureTask<String> ft1 = new FutureTask<String>(ca1); new Thread(ft1).start(); // 等包子 -- 必须要等待返回的结果,所以要调用join方法 Callable ca2 = new Callable(){ @Override public Object call() throws Exception { try { Thread.sleep(1000*3); } catch (InterruptedException e) { e.printStackTrace(); } return "包子准备完毕"; } }; FutureTask<String> ft2 = new FutureTask<String>(ca2); new Thread(ft2).start(); System.out.println(ft1.get()); System.out.println(ft2.get()); long end = System.currentTimeMillis(); System.out.println("准备完毕时间:"+(end-start)); }
  • 相关阅读:
    SQL Union 和Union All 的区别
    SqlHelper.cs
    转载WPF:创建你的第一个WPF项目
    数据库分页存储过程
    sql 常用基础查询
    创建表--自动编号字段且自增
    模糊查询
    模式的定义
    C#计算两个日期之间相差的天数
    优化SQL查询:如何写出高性能SQL语句
  • 原文地址:https://www.cnblogs.com/ZJOE80/p/12917321.html
Copyright © 2011-2022 走看看