java future用法_Java并发系列(10)——FutureTask 和 CompletionService

本文详细介绍了Java并发工具类FutureTask和CompletionService的原理与使用。FutureTask作为有返回值的异步调用基础,涉及任务的执行、取消和结果获取。CompletionService则提供了一种获取最先完成任务结果的方式,有效避免长任务阻塞短任务的处理。通过示例展示了CompletionService如何提高任务处理效率。

接上一篇《Java并发系列(9)——并发工具类

8 FutureTask 与 CompletionService

把 FutureTask 和 CompletionService 放在一起,并不是因为它们之间有什么特别的联系,虽然确实有一点联系。

主要是因为下一章要讲线程池,如果不把 FutureTask 和 CompletionService 搞清楚,线程池的部分代码看得会比较困惑。

8.1 FutureTask

FutureTask 的实现比较简单,但它是一个非常基础的东西。只要涉及到有返回值的异步调用,或直接或间接一般都会用到它。

用法很简单,这里给个示例:

package 

8.1.1 类图

583a5f4687e2a2f3503bf8bde11cdd03.png

8.1.2 几个问题

FutureTask 实现了 Future 接口的 5 个方法,以及 Runnable 接口的 run 方法。

其实从 FutureTask 的用法很容易猜出,它内部持有一个 Runnable 或 Callable,调用它的 run 方法时会被代理到 Runnable 或 Callable 的 run 方法去。

但还有几个细节问题:

  • 怎样 cancel 一个任务?
    • 如果任务尚未执行,怎么办?
    • 如果任务正在执行,怎么办?
    • 如果任务已经执行完了,怎么办?
  • 怎样算是 isDone?
    • 如果任务尚未执行,算不算?
    • 如果任务正在执行,算不算?
    • 如果任务执行成功,算不算?
    • 如果任务执行抛出未捕获异常,算不算?
    • 如果任务尚未执行、正在执行、执行完成被 cancel 了,这几种情况分别算不算?

8.1.3 实现细节

8.1.3.1 属性

共 5 个:

  • state:int 类型,从 0 ~ 6 记录了 FutureTask 的 7 种状态;
  • callable:Callable 类型,待执行的任务,Runnable 会被包装成 Callable;
  • outcome:Object 类型,记录执行结果,即 Callable 的返回值,或者也可能是个 Throwable;
  • runner:Thread 类型,记录正在执行的线程;
  • waiters:WaitNode 类型,记录正在阻塞等待结果的线程,保存的是链表的第一个节点。

8.1.3.2 run 方法

主干逻辑:

public 

run 方法异常退出分支,走 setException 方法:

//处理 run 方法异常退出

run 方法成功执行分支,走 set 方法:

protected 

处理可能遗漏的 interrupt:

private 

如果状态是 INTERRUPTING,说明调用 cancel 方法的线程还没有结束,在这里要等它跑完。

这里是一个 while 循环,直到状态被改掉为止,通过 yield 试图把 cpu 让给其它线程,也是希望 cancel 线程能早点拿到 cpu 资源早点跑完。

最后,不管是 setException 还是 set 方法在赋值给 outcome 之后都会调用:

private 

run 方法总结:

  • 主要做三件事:
    • 执行任务;
    • 保存任务执行结果;
    • 唤醒阻塞的线程来拿结果;
  • callable 属性:主要就是执行 callable 对象的 run 方法,执行完置为 null,因此只会执行一次;
  • runner 属性:控制 Callable#run 方法的逻辑不会被多个线程并发执行;
  • outcome 属性:
    • Callable#run 方法抛出异常,outcome 属性保存这个 Throwable;
    • Callable#run 方法正常退出,outcome 属性保存其返回值;
  • waiters 属性:Callable#run 方法执行完毕,不论成功还是异常,outcome 属性赋值后,遍历链表唤醒所有阻塞的线程;
  • state 属性:
    • 直到 outcome 赋值之前,一直都是 NEW;
    • 成功执行,状态变更:NEW -> COMPLETING -> NORMAL;
    • 异常执行,状态变更:NEW -> COMPLETING -> EXCEPTIONAL。

8.1.3.3 get 方法

主干逻辑:

public 

阻塞逻辑:

private 

退出阻塞之后,获取执行结果的逻辑:

private 

get 方法总结:

  • 用链表保存了所有在 get 方法阻塞的线程;
  • 链表采用头插法,后阻塞的线程排在前面,不算超时自己醒来的的线程,会先被唤醒;
  • get 会得到三种结果:
    • 任务正常完成:得到任务返回值;
    • 任务异常退出:抛出 ExecutionException,里面包装了任务抛出的异常;
    • 任务被取消:抛出 CancellationException。

8.1.3.4 cancel 方法

主干逻辑:

//取消任务,成功取消返回 true,否则返回 false

cancel 方法总结:

  • cancel 会失败的情况:run 线程任务执行已经出了结果,不论是正常执行结束或者抛出异常;
  • cancel 会成功的情况,又分两种不同的处理逻辑:
    • 需要打断:如果任务已经执行,将其打断,最终以 INTERRUPTED 状态结束;
    • 不需要打断:如果任务已经执行,放任其不管,最终以 CANCELLED 状态结束;
    • 对于尚未执行的任务,以上两种情况,仅最终状态不同,没有其它区别;
  • 对于唤醒阻塞线程:
    • cancel 成功:需要唤醒;
    • cancel 失败:不需要唤醒;
  • cancel 方法的影响:
    • 如果返回 false,即 cancel 失败:cancel 方法什么都没干,没有任何影响;
    • 如果返回 true,即 cancel 成功:cancel(true) 会打断 run 线程,cancel(false) 不会,然后它们都会唤醒阻塞线程,被唤醒的线程 get 执行结果会抛出一个 CancellationException。

8.1.3.5 七种状态汇总

FutureTask 的 7 种状态:

  • NEW(0):尚未执行,或正在执行;
  • COMPLETING(1):执行完成,还没有保存结果;
  • NORMAL(2):正常完成,已保存了任务执行的返回值;
  • EXCEPTIONAL(3):异常退出,已保存了任务抛出的异常;
  • CANCELLED(4):任务被取消,如果取消的时候,任务还没执行,不会再执行;如果正在执行,让其继续跑下去;
  • INTERRUPTING(5):任务正在被取消,如果任务还没执行,不会再执行;如果正在执行,紧接着将其打断(任务是否响应这个打断是另外一回事);
  • INTERRUPTED(6):任务已被取消,如果取消的时候,任务还没执行,不会再执行;如果正在执行,那么此时任务已经被打断过了,任务有没有理会这个打断,FutureTask 无法得知。

状态变更的四种情况:

  • 正常执行:NEW -> COMPLETING -> NORMAL;
  • 执行异常:NEW -> COMPLETING -> EXCEPTIONAL;
  • cancel(true) 成功:NEW -> INTERRUPTING -> INTERRUPTED;
  • cancel(false) 成功:NEW -> CANCELLED;
  • cancel(true/false) 失败:对任务执行没有任何影响,即前两种情况。

状态分类:

  • 从是否安全的维度:
    • 非安全状态:NEW,所有线程都可以把 NEW 状态改为其它状态,要考虑并发安全问题;
    • 安全状态:其它所有状态,一个线程看到状态不为 NEW,要么等待状态再次变更,要么直接返回,不能做任何事,所以不存在并发安全问题;
  • 从阶段的维度:
    • 初始态:NEW;
    • 中间态:COMPLETING,INTERRUPTING;
    • 最终态:NORMAL,EXCEPTIONAL,INTERRUPTED,CANCELLED。

8.1.3.6 isCancelled 方法

public 

8.1.3.7 isDone 方法

public 

8.2 CompletionService

后面要讲的 ExecutorService 的实现会用到 CompletionService,但 CompletionService 实际上也会用到 Executor,所以涉及到 Executor 的部分留到后面再讲。

8.2.1 接口方法

也不复杂,共 5 个:

  • submit(Callable<V>):Future<V>,非阻塞方法,提交一个 Callable 任务,返回 Future,用于获取结果;
  • submit(Runnable, V):Future<V>,非阻塞方法,提交一个 Runnable 任务,返回 Future,主要用于感知任务是否执行完成,因为任务返回的值是已知的,就是自己在参数里传入的;
  • take():Future<V>,阻塞方法,等待直到接下来第一个任务执行完成,取走其 Future;
  • poll():Future<V>,非阻塞方法,取走目前第一个执行完成的任务的 Future,如果没有已经执行完成的立即返回 null;
  • poll(long,TimeUnit):Future<V>,阻塞方法,取走目前第一个执行完成的任务的 Future,如果没有已经执行完成的,可以等待指定的时间。

它的核心功能主要在后面三个方法,即通过 submit 提交一批任务,然后 take 或者 poll 拿到最先完成的任务。

这样可以避免,前面执行的任务耗时很长,导致后面执行的任务已经完成了也拿不到结果。

8.2.2 demo

package 

不用 CompletionService,执行结果如下,耗时 20 秒:

processed 

用 CompletionService,执行结果如下,耗时 11 秒:

processed 

出现这样的结果是因为,这 10 个任务,前面的耗时较长,后面的耗时较短。

虽然都是提交给线程池执行,但是不用 CompletionService 的情况,傻傻地等第一个耗时最长的任务,后面的任务已经执行完成了也不先拿出来去处理,导致总耗时:等待任务结果 10 秒 + 处理结果 10 秒 = 20 秒。

而使用了 CompletionService,耗时最短的任务结果出来了,先拿去处理,这样总耗时:等待任务结果 10 秒 + 处理结果 1 秒 = 11 秒。

8.2.3 实现

实现其实很简单,看到 take,poll 这些方法,显然是一个 BlockingQueue。

在 ExecutorCompletionService 类里面:

private 

主要就这个 QueueingFuture,重写了 FutureTask 的 done 方法,任务执行完成后,把执行结果放到队列里面。


Java并发系列(10)--FutureTask 和 CompletionService​blog.csdn.net
f6a8281da65e0b29823369e4d7f4fc7c.png
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值