多线程系列(十九) -Future使用详解

打印 上一主题 下一主题

主题 883|帖子 883|积分 2649

一、择要

在前几篇线程系列文章中,我们介绍了线程池的相关技能,任务执行类只必要实现Runnable接口,然后交给线程池,就可以轻松的实现异步执行多个任务的目标,提升步调的执行效率,好比如下异步执行任务下载。
  1. // 创建一个线程池
  2. ExecutorService executor = Executors.newFixedThreadPool(2);
  3. // 提交任务
  4. executor.submit(new Runnable() {
  5.     @Override
  6.     public void run() {
  7.         // 执行下载某文件任务
  8.         System.out.println("执行下载某文件任务");
  9.     }
  10. });
复制代码
而现实上Runnable接口并不能满意全部的需求,好比有些场景下,我们想要获取任务执行的返回结果,Runnable接口因为无返回值,只能想办法通过额外的方式来写入和读取,操纵起来十分不便。
因此,从 JDK 1.5 开始,Java 标准库提供了一个Callable接口,与Runnable接口相比,它的方法上多了一个返回值;同时Callable是一个泛型接口,可以返回指定类型的结果,好比如下的实现类!
  1. public class Task implements Callable<String> {
  2.     @Override
  3.     public String call() throws Exception {
  4.         // 执行下载某文件任务
  5.         System.out.println("执行下载某文件任务");
  6.         return "xxx";
  7.     }
  8. }
复制代码
题目来了,如何获取异步执行的结果呢?
在 JDK 1.5 中,Java 标准库还提供了一个Future接口,它可以用来获取异步执行的结果。
下面我们一起来了解一下这个Future接口!
二、Future

Future接口,表示一个大概还没有完成异步任务的结果,它提供了查抄任务是否已完成、以及等待任务完成并获取结果等方法。
如果看过ExecutorService.submit()方法,会发现它的返回参数都是Future类型,Future类型的实例可以用来获取异步任务执行的结果。
下面我们先来看一个简单的示例,以便于更好的明白!
  1. public class Task implements Callable<String> {
  2.    
  3.     @Override
  4.     public String call() throws Exception {
  5.         // 执行下载某文件任务,并返回文件名称
  6.         System.out.println("thread name:" +  Thread.currentThread().getName() + " 开始执行下载任务");
  7.         return "xxx.png";
  8.     }
  9. }
复制代码
  1. public class FutureTest {
  2.     public static void main(String[] args) throws Exception {
  3.         // 创建一个线程池
  4.         ExecutorService executor = Executors.newFixedThreadPool(1);
  5.         // 初始化一个任务
  6.         Callable<String> task = new Task();
  7.         // 提交任务并获得Future的实例
  8.         Future<String> future = executor.submit(task);
  9.         // 从Future获取异步执行返回的结果(可能会阻塞等待结果)
  10.         String result =future.get();
  11.         System.out.println("任务执行结果:" +  result);
  12.         // 任务执行完毕之后,关闭线程池(可选)
  13.         executor.shutdown();
  14.     }
  15. }
复制代码
输出结果如下:
  1. thread name:pool-1-thread-1 开始执行下载任务
  2. 任务执行结果:xxx.png
复制代码
从以上的示例可以清晰的看到,当必要获取异步线程的执行结果返回值时,通常必要搭配使用Future和Callable接口来实现,大体可以用如下步调来概括:

  • 1.起首提交一个实现Callable接口的任务到线程池中
  • 2.然后获取一个Future类型的对象
  • 3.最后在主线程中调用Future对象的get()方法,如果异步任务执行完成,就可以直接获得结果;如果异步任务执行没有完成,get()方法会阻塞,直到任务执行完成后才能获取结果
分析源码你会发现,Callable接口主要用途是界说一个支持返回结果的方法;重点实现主要集中在Future接口上。
下面我们重点来看下Future接口方法!
2.1、Future 接口方法

方法描述get()获取结果(会阻塞等待)get(long timeout, TimeUnit unit)在指定的时间内获取结果,如果超时,会抛非常并退出等待状态cancel(boolean mayInterruptIfRunning)实验取消当前任务,当传入参数为true时,表示实验中断任务的执行,false表示不中断,继续执行直到完成,如果取消乐成,返回true;反之falseisCancelled()判断任务是否已取消isDone()判断任务是否已完成2.2、Future 接口实现类

Future本质实在是一个接口,并不是具体的实现类,真正负责工作的还是它的实现类来完成。
我们还是以上文的线程池ExecutorService.submit()方法为例,看看它用的是哪种实现类!
分析一下源码,会发现线程池用的实现类是FutureTask,关键核心源码如下:
  1. protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
  2.     return new FutureTask<T>(callable);
  3. }
复制代码
FutureTask类是一个实现了Future接口全部功能的具体类,可直接使用它来实现获取异步任务执行的结果值。
FutureTask的工作原理实在也并不复杂,它接受一个Callable或者Runnable对象作为参数,然后在线程池执行器中执行该任务,最后通过get()方法可以同步等待获取任务的执行结果。
真正起到关键作用的是,在FutureTask内部,封装了一个状态变量,用于记录任务的状态(等待、运行、完成、取消等),以及任务执行结果或非常信息,通过该状态变量,我们可以判断任务是否已完成、以及获取任务的执行结果等信息。
因为FutureTask也实现了Runnable接口,因此我们也可以将FutureTask作为任务,提交给线程池执行器。
具体示例如下:
  1. public class FutureTest {
  2.     public static void main(String[] args) throws Exception {
  3.         // 1.创建一个线程池
  4.         ExecutorService executor = Executors.newFixedThreadPool(1);
  5.         // 2.初始化一个任务
  6.         Callable<String> callable = new Task();
  7.         // 3.创建FutureTask对象
  8.         FutureTask<String> futureTask = new FutureTask<>(callable);
  9.         // 4.提交任务给执行器执行
  10.         executor.execute(futureTask);
  11.         // 5.获取任务的执行结果
  12.         String result = futureTask.get(3, TimeUnit.SECONDS);
  13.         System.out.println("任务执行结果:" +  result);
  14.         // 6.关闭线程池(可选)
  15.         executor.shutdown();
  16.     }
  17. }
复制代码
输出结果同上!
如果想实验取消任务的执行,也可以通过如下方式来实现!
  1. boolean isSuccess = futureTask.cancel(true);
  2. System.out.println("任务是否取消成功:" +  isSuccess);
复制代码
除此之外,如果仔细的分析Future接口的类关系,会发现它的实现类非常的多,FutureTask只是它的一个基础实现类而已,部分类关系图如下!

其它常用实现类简介:

  • CompletableFuture:支持传入回调对象,当异步任务完成或者发生非常时,自动调用回调对象的回调方法
  • ForkJoinTask:支持把一个大任务拆成多个小任务,然后并行执行,在多核 CPU 上可以明显提升步调的执行效率
  • ScheduledFuture:支持周期性定时的执行任务,其中ScheduledFutureTask是一个私有类,只能通过ScheduledThreadPoolExecutor初始化操纵
关于CompletableFuture、ForkJoinTask和ScheduledFuture,我们会在背面的文章中,再次单独介绍具体的用法。
三、小结

本文主要围绕Future接口用法做了一次简单的知识总结,其中FutureTask类是Future接口中一个非常告急的实现类,通过它可以获取异步任务执行的返回值,通常用于异步计算带有返回值的任务。
限于篇幅的原因,本文没有对FutureTask做过深入的原理解说,主要围绕具体用法进行介绍,有兴趣的朋友可以阅读这篇文章《Java的Future机制详解》,以便更清晰的了解它的实现原理。
如果有描述不对的地方,欢迎留言指出,共同进步!
四、参考

1.https://www.liaoxuefeng.com/wiki/1252599548343744/1306581155184674
2.https://www.cnblogs.com/xrq730/p/4872722.html
3.https://juejin.cn/post/7231074060787908663
4.https://zhuanlan.zhihu.com/p/54459770

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

美丽的神话

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表