Future 类

Future 的作用
Future 最主要的作用是,比如当做一定运算的时候,运算过程可能比较耗时,有时会去查数据库,或是繁重的计算,比如压缩、加密等,在这种情况下,如果我们一直在原地等待方法返回,显然是不明智的,整体程序的运行效率会大大降低。我们可以把运算的过程放到子线程去执行,再通过 Future 去控制子线程执行的计算过程,最后获取到计算结果。这样一来就可以把整个程序的运行效率提高,是一种异步的思想。

Callable 和 Future 的关系

接下来我们介绍下 Callable 和 Future 的关系,前面讲过,Callable 接口相比于 Runnable 的一大优势是可以有返回结果,那这个返回结果怎么获取呢?就可以用 Future 类的 get 方法来获取 。因此,Future 相当于一个存储器,它存储了 Callable 的 call 方法的任务结果。除此之外,我们还可以通过 Future 的 isDone 方法来判断任务是否已经执行完毕了,还可以通过 cancel 方法取消这个任务,或限时获取任务的结果等,总之 Future 的功能比较丰富。有了这样一个从宏观上的概念之后,我们就来具体看一下 Future 类的主要方法。

Future 的方法和用法

首先看一下 Future 接口的代码,一共有 5 个方法,代码如下所示:

复制代码
public interface Future<V> {

boolean cancel(boolean mayInterruptIfRunning);

boolean isCancelled();

boolean isDone();

V get() throws InterruptedException, ExecutionException;

V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutExceptio
}
其中,第 5 个方法是对第 4 个方法的重载,方法名一样,但是参数不一样。

get() 方法:获取结果
get 方法最主要的作用就是获取任务执行的结果,该方法在执行时的行为取决于 Callable 任务的状态,可能会发生以下 5 种情况。

(1)最常见的就是当执行 get 的时候,任务已经执行完毕了,可以立刻返回,获取到任务执行的结果。

(2)任务还没有结果,这是有可能的,比如我们往线程池中放一个任务,线程池中可能积压了很多任务,还没轮到我去执行的时候,就去 get 了,在这种情况下,相当于任务还没开始;还有一种情况是任务正在执行中,但是执行过程比较长,所以我去 get 的时候,它依然在执行的过程中。无论是任务还没开始或在进行中,我们去调用 get 的时候,都会把当前的线程阻塞,直到任务完成再把结果返回回来。

(3)任务执行过程中抛出异常,一旦这样,我们再去调用 get 的时候,就会抛出 ExecutionException 异常,不管我们执行 call 方法时里面抛出的异常类型是什么,在执行 get 方法时所获得的异常都是 ExecutionException。

(4)任务被取消了,如果任务被取消,我们用 get 方法去获取结果时则会抛出 CancellationException。

(5)任务超时,我们知道 get 方法有一个重载方法,那就是带延迟参数的,调用了这个带延迟参数的 get 方法之后,如果 call 方法在规定时间内正常顺利完成了任务,那么 get 会正常返回;但是如果到达了指定时间依然没有完成任务,get 方法则会抛出 TimeoutException,代表超时了。

下面用图的形式让过程更清晰:

在图中,右侧是一个线程池,线程池中有一些线程来执行任务。重点在图的左侧,可以看到有一个 submit 方法,该方法往线程池中提交了一个 Task,这个 Task 实现了 Callable 接口,当我们去给线程池提交这个任务的时候,调用 submit 方法会立刻返回一个 Future 类型的对象,这个对象目前内容是空的,其中还不包含计算结果,因为此时计算还没有完成。

当计算一旦完成时,也就是当我们可以获取结果的时候,线程池便会把这个结果填入到之前返回的 Future 中去(也就是 f 对象),而不是在此时新建一个新的 Future。这时就可以利用 Future 的 get 方法来获取到任务的执行结果了。

我们来看一个代码示例:

复制代码
/**
 * 描述:     演示一个 Future 的使用方法
 */
public class OneFuture {

public static void main(String[] args) {
        ExecutorService service = Executors.newFixedThreadPool(10);
        Future<Integer> future = service.submit(new CallableTask());
        try {
            System.out.println(future.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        service.shutdown();
    }

static class CallableTask implements Callable<Integer> {

@Override
        public Integer call() throws Exception {
            Thread.sleep(3000);
            return new Random().nextInt();
        }
    }
}
在这段代码中,main 方法新建了一个 10 个线程的线程池,并且用 submit 方法把一个任务提交进去。这个任务如代码的最下方所示,它实现了 Callable 接口,它所做的内容就是先休眠三秒钟,然后返回一个随机数。接下来我们就直接把 future.get 结果打印出来,其结果是正常打印出一个随机数,比如 100192 等。这段代码对应了我们刚才那个图示的讲解,这也是 Future 最常用的一种用法。

isDone() 方法:判断是否执行完毕
下面我们再接着看看 Future 的一些其他方法,比如说 isDone() 方法,该方法是用来判断当前这个任务是否执行完毕了。

需要注意的是,这个方法如果返回 true 则代表执行完成了;如果返回 false 则代表还没完成。但这里如果返回 true,并不代表这个任务是成功执行的,比如说任务执行到一半抛出了异常。那么在这种情况下,对于这个 isDone 方法而言,它其实也是会返回 true 的,因为对它来说,虽然有异常发生了,但是这个任务在未来也不会再被执行,它确实已经执行完毕了。所以 isDone 方法在返回 true 的时候,不代表这个任务是成功执行的,只代表它执行完毕了。

我们用一个代码示例来看一看,代码如下所示:

复制代码
public class GetException {

public static void main(String[] args) {
        ExecutorService service = Executors.newFixedThreadPool(20);
        Future<Integer> future = service.submit(new CallableTask());

try {
            for (int i = 0; i < 5; i++) {
                System.out.println(i);
                Thread.sleep(500);
            }
            System.out.println(future.isDone());
            future.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }

static class CallableTask implements Callable<Integer> {

@Override
        public Integer call() throws Exception {
            throw new IllegalArgumentException("Callable抛出异常");
        }
    }
}
在这段代码中,可以看到有一个线程池,并且往线程池中去提交任务,这个任务会直接抛出一个异常。那么接下来我们就用一个 for 循环去休眠,同时让它慢慢打印出 0 ~ 4 这 5 个数字,这样做的目的是起到了一定的延迟作用。在这个执行完毕之后,再去调用 isDone() 方法,并且把这个结果打印出来,然后再去调用 future.get()。

这段代码的执行结果是这样的:

复制代码
0
1
2
3
4
true
java.util.concurrent.ExecutionException: java.lang.IllegalArgumentException: Callable抛出异常
...
这里要注意,我们知道这个异常实际上是在任务刚被执行的时候就抛出了,因为我们的计算任务中是没有其他逻辑的,只有抛出异常。我们再来看,控制台是什么时候打印出异常的呢?它是在 true 打印完毕后才打印出异常信息的,也就是说,在调用 get 方法时打印出的异常。

这段代码证明了三件事情:第一件事情,即便任务抛出异常,isDone 方法依然会返回 true;第二件事情,虽然抛出的异常是 IllegalArgumentException,但是对于 get 而言,它抛出的异常依然是 ExecutionException;第三个事情,虽然在任务执行一开始时就抛出了异常,但是真正要等到我们执行 get 的时候,才看到了异常。

cancel 方法:取消任务的执行
下面我们再来看一下 cancel 方法,如果不想执行某个任务了,则可以使用 cancel 方法,会有以下三种情况:

第一种情况最简单,那就是当任务还没有开始执行时,一旦调用 cancel,这个任务就会被正常取消,未来也不会被执行,那么 cancel 方法返回 true。

第二种情况也比较简单。如果任务已经完成,或者之前已经被取消过了,那么执行 cancel 方法则代表取消失败,返回 false。因为任务无论是已完成还是已经被取消过了,都不能再被取消了。

第三种情况比较特殊,就是这个任务正在执行,这个时候执行 cancel 方法是不会直接取消这个任务的,而是会根据我们传入的参数做判断。cancel 方法是必须传入一个参数,该参数叫作  mayInterruptIfRunning,它是什么含义呢?如果传入的参数是 true,执行任务的线程就会收到一个中断的信号,正在执行的任务可能会有一些处理中断的逻辑,进而停止,这个比较好理解。如果传入的是 false 则就代表不中断正在运行的任务,也就是说,本次 cancel 不会有任何效果,同时 cancel 方法会返回 false。

那么如何选择传入 true 还是 false 呢?

传入 true 适用的情况是,明确知道这个任务能够处理中断。

传入 false 适用于什么情况呢?

如果我们明确知道这个线程不能处理中断,那应该传入 false。
我们不知道这个任务是否支持取消(是否能响应中断),因为在大多数情况下代码是多人协作的,对于这个任务是否支持中断,我们不一定有十足的把握,那么在这种情况下也应该传入 false。
如果这个任务一旦开始运行,我们就希望它完全的执行完毕。在这种情况下,也应该传入 false。
这就是传入 true 和 false 的不同含义和选择方法。

isCancelled() 方法:判断是否被取消
最后一个方法是 isCancelled 方法,判断是否被取消,它和 cancel 方法配合使用,比较简单。

以上就是关于 Future 的主要方法的介绍了。

用 FutureTask 来创建 Future
除了用线程池的 submit 方法会返回一个 future 对象之外,同样还可以用 FutureTask 来获取 Future 类和任务的结果。

FutureTask 首先是一个任务(Task),然后具有 Future 接口的语义,因为它可以在将来(Future)得到执行的结果。

我们来看一下 FutureTask 的代码实现:

复制代码
public class FutureTask<V> implements RunnableFuture<V>{
 ...
}
可以看到,它实现了一个接口,这个接口叫作 RunnableFuture。我们再来看一下 RunnableFuture 接口的代码实现:

复制代码
public interface RunnableFuture<V> extends Runnable, Future<V> {
    void run();
}
可以看出,它是 extends Runnable 和 Future 这两个接口的,它们的关系如下图所示:

既然 RunnableFuture 继承了 Runnable 接口和 Future 接口,而 FutureTask 又实现了 RunnableFuture 接口,所以 FutureTask 既可以作为 Runnable 被线程执行,又可以作为 Future 得到 Callable 的返回值。

典型用法是,把 Callable 实例当作 FutureTask 构造函数的参数,生成 FutureTask 的对象,然后把这个对象当作一个 Runnable 对象,放到线程池中或另起线程去执行,最后还可以通过 FutureTask 获取任务执行的结果。

下面我们就用代码来演示一下:

复制代码
/**
 * 描述:     演示 FutureTask 的用法
 */
public class FutureTaskDemo {

public static void main(String[] args) {
        Task task = new Task();
        FutureTask<Integer> integerFutureTask = new FutureTask<>(task);
        new Thread(integerFutureTask).start();

try {
            System.out.println("task运行结果:"+integerFutureTask.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

class Task implements Callable<Integer> {

@Override
    public Integer call() throws Exception {
        System.out.println("子线程正在计算");
        int sum = 0;
        for (int i = 0; i < 100; i++) {
            sum += i;
        }
        return sum;
    }
}
在这段代码中可以看出,首先创建了一个实现了 Callable 接口的 Task,然后把这个 Task 实例传入到 FutureTask 的构造函数中去,创建了一个 FutureTask 实例,并且把这个实例当作一个 Runnable 放到 new Thread() 中去执行,最后再用 FutureTask 的 get 得到结果,并打印出来。

执行结果是 4950,正是任务里 0+1+2+...+99 的结果。

引用:https://kaiwu.lagou.com/course/courseInfo.htm?courseId=16#/detail/pc?id=288

Java多线程学习三十三:Future 的主要功能是什么?相关推荐

  1. Java多线程学习三十四:使用 Future 有哪些注意点?Future 产生新的线程了吗

    Future 的注意点 1. 当 for 循环批量获取 Future 的结果时容易 block,get 方法调用时应使用 timeout 限制 对于 Future 而言,第一个注意点就是,当 for ...

  2. Java多线程学习三十八:你知道什么是 CAS 吗

    CAS 简介 CAS 其实是我们面试中的常客,因为它是原子类的底层原理,同时也是乐观锁的原理,所以当你去面试的时候,经常会遇到这样的问题"你知道哪些类型的锁"?你可能会回答&quo ...

  3. Java多线程学习四十三:

    本课时我们主要讲解 final 的三种用法. final 的作用 final 是 Java 中的一个关键字,简而言之,final 的作用意味着"这是无法改变的".不过由于 fina ...

  4. Java多线程学习三十七:volatile 的作用是什么?与 synchronized 有什么异同

    volatile 是什么 首先我们就来介绍一下 volatile,它是 Java 中的一个关键字,是一种同步机制.当某个变量是共享变量,且这个变量是被 volatile 修饰的,那么在修改了这个变量的 ...

  5. Java多线程学习三十六:主内存和工作内存的关系

    CPU 有多级缓存,导致读的数据过期 由于 CPU 的处理速度很快,相比之下,内存的速度就显得很慢,所以为了提高 CPU 的整体运行效率,减少空闲时间,在 CPU 和内存之间会有 cache 层,也就 ...

  6. Java多线程学习二十三:什么是阻塞队列

    阻塞队列的作用 阻塞队列,也就是 BlockingQueue,它是一个接口,如代码所示: public interface BlockingQueue<E> extends Queue&l ...

  7. Java多线程学习三十二:Callable 和 Runnable 的不同?

    为什么需要 Callable?Runnable 的缺陷 先来看一下,为什么需要 Callable?要想回答这个问题,我们先来看看现有的 Runnable 有哪些缺陷? 不能返回一个返回值 第一个缺陷, ...

  8. Java多线程学习三十一:ThreadLocal 是用来解决共享资源的多线程访问的问题吗?

    ThreadLocal 是不是用来解决共享资源的多线程访问的. 这是一个常见的面试问题,如果被问到了 ThreadLocal,则有可能在你介绍完它的作用.注意点等内容之后,再问你:ThreadLoca ...

  9. Java多线程学习三十:ThreadLocal 适合用在哪些实际生产的场景中

    我们在学习一个工具之前,首先应该知道这个工具的作用,能带来哪些好处,而不是一上来就闷头进入工具的 API.用法等,否则就算我们把某个工具的用法学会了,也不知道应该在什么场景下使用.所以,我们先来看看究 ...

最新文章

  1. 人民广场怎么走? 地铁换乘算法的实现 MikeTech | MikeTech
  2. vb.net2019-读逗号分隔csv等文件
  3. 删除副本列表中的消失项目符号
  4. 17.判断一个整数是否是回文数
  5. 机器视觉烟包行业质量检测解决方案
  6. oracle基本操作语句大全
  7. 深度学习MatConvNet安装
  8. mov转换成mp4,mov转mp4方法
  9. 2021年最新独立版橙色去水印微信小程序-更新2021.8.31
  10. 当今社会到底更需要专才还是通才
  11. 如何进行拼图?拼图其实很简单
  12. 黑色曼陀罗的宿命,无尽的结束,留下伤痛与悲残
  13. 基于android系统的单词学习软件设计与开发【附项目源码+论文说明】分享
  14. SLAM在机器人中的应用
  15. setTimeout函数的参数
  16. Excel中VLOOKUP跨文件查找
  17. CSDN校园活动社开展“开学季活动”
  18. 如何使用CC协议声明
  19. 5G NR LDPC编译码汇总
  20. 使用spark-shell操作Carbondata

热门文章

  1. 消暑圣品!这部宝马“鬼片”你看过没,第一个被无人驾驶车辆吓跑的女鬼?...
  2. 宁德时代811电芯初现真容 搭配宝马X1混动汽车能量密度提升近6成
  3. 华为的接班人要具备哪些能力?任正非这样说...
  4. 得罪全球首富的下场?曝光贝佐斯婚外情丑闻的小报将被卖身...
  5. 美团点评:摩拜贡献收入15亿元 同期亏损45.5亿元
  6. 2019年第一批重点作品版权保护预警名单公布 都是贺岁档
  7. 朱海舟宣布新一批应用已经适配锤子TNT 网友:救救海舟
  8. 走捷径拿到大厂25K高级测试Offer,别不服!
  9. Jmeter分布式压测实战及踩坑处理(含参数化)
  10. 当今互联网从业者必看书籍 -- 《自传播》