介绍:
FutureTask是一个可取消的异步计算。
1.通过get方法异步的获取任务执行结果
2.通过cancel方法来尝试取消正在执行的任务
3.通过get方法来捕获线程内部执行任务时抛出的异常
那么,大家是否知道上述这些方法的底层呢,为什么get方法能获取到任务的结果?cancel方法是怎么样取消任务的?通过get方法为什么能捕获到内部线程执行任务时抛出的异常,是怎么样实现的?
接下来让我们带着问题在源码中寻找答案。
!!!嘎嘎详细,看完后保证你会豁然开朗!!!
1.FutureTask的类图
首先,我们看一下FutureTask的类图
FutureTask实现了RunnableFuture接口。
Runnable接口:这意味着它既可以作为一个任务提交给线程执行
Future接口:这意味着它可以用来获取异步计算的结果
2.源码解析
2.1 构造函数
我们先从构造函数入手
FutureTask支持两种传参
1.第一种传递一个Callable,Callable接口定义了一个可以返回值或抛出异常的操作。
2.第二种传递了一个Runnable和result,进入到底层可以看到,最终Runnable和result都被封装成了Callable。如下:
经过构造函数后,我们的任务已经被保存到成员变量中了,并且FutureTask的状态被初始化为NEW(新建状态)。
2.2 内部的run方法
我们在构建了一个FutureTask后,第二步就是将它扔到线程池中去执行了。
上述的类图可以看到FutureTask继承了Runnable接口,那么当FutureTask在线程中执行时,那就是执行它的run方法了。接下来让我们就进入到它的run方法中。
标签1:这里执行了我们的任务,并返回了结果
标签2:这里的set方法,传参是result结果,那这里面一定就是处理返回结果的地方了。(后面会详细讲解这个方法)
标签3:执行任务中出现的异常被setException方法给处理了,我们知道FutureTask的get方法是能够捕获并抛出内部线程执行中的异常的,那么,关于get方法捕获内部线程抛出的异常的奥秘,就一定在这个setException方法里面了。
进入到set方法和setException方法中:
这两方法的逻辑相同,都是先将我们任务的状态由新建改成了完成中
并且将result或者异常对象都放到了成员变量outcome(这个名字一听就是到时候会返回的东西了)
这里只是将异常放到了outcome变量中啊,并没有抛出去。
没错,这里只是暂存到outcome中呢,那我这里就先给大家解开疑惑。(后面还会讲解report方法的)
这里能看到,在最后获取结果的逻辑中,我们的任务执行中的异常被封装到了ExecutionException中并抛了出去,因此用户可以通过get方法中抛出的ExecutionException异常来拿到内部线程执行任务中抛出的异常。
2.3 get方法
当我们执行了任务后,会通过FutureTask的get方法来获取返回结果
我们进入到get方法中,看看get方法做了什么操作。
这里首先介绍一下任务的状态(方便后续阅读),任务状态一共有7种。
NEW: 表示任务刚被创建,还未开始执行。
COMPLETING: 任务已经执行完成,但结果或异常还未设置到相应的字段中。
NORMAL: 任务正常完成,并且结果已经成功设置。
EXCEPTIONAL: 任务执行过程中抛出了异常,异常已经设置到相应字段。
CANCELLED: 任务被取消。
INTERRUPTING: 任务正在被中断处理过程中。
INTERRUPTED: 任务已经被中断完成。
进入到get方法中
方法1:阻塞等待获取结果
方法2:非阻塞,超时了会抛异常
2.4 awaitDone方法
在get方法中,可以看到,当任务的状态小于COMPLETING时(也就是任务处于新建状态时),都会进入到awaitDone方法中。
awaitDone方法内部有一个for循环
我们逐行解析内部的逻辑
标签1:
这里会判断线程是否中断(这个线程不是执行任务的线程,执行任务的线程会保存在成员变量runner中。这里的线程是执行FutureTask的方法的线程),当检测到线程被中断就会抛出异常。
标签2:
判断了线程的状态是否已完成,完成了就会返回状态值
标签3:
如果任务处于完成中的状态,就暂时释放当前cpu的资源
为什么要释放呢?
因为你的任务已完成,但是可能还需要等1秒中才出结果,那么你就不要一直在for循环中循环查询结果浪费cpu资源了,请暂时把cpu资源给更重要的任务吧,等下次cpu时间片资源分配给你的时候,你再返回结果也不迟。
标签4:
节点如果为空,就创建一个新的节点,为后续的入队做准备。
标签5:
判断节点入队了没,发现节点没入队,就给标签4创建的节点入队(入队操作其实就是将这个节点给放到链表的头位置)
标签6:
get方法设置了超时时间。
已超时:直接返回状态值
未超时:挂起到超时的时间点(中途如果任务执行结束了,就请唤醒我,就会返回状态值了。不然等到我挂起到超时时间点了,会自动唤醒,也会返回状态值)
标签7:
直接挂起当前线程了,免得浪费cpu资源,等结果出来了在唤醒我吧,不然我就一直挂起了(这里就是get方法就会一直阻塞的原因)
至此,awaitDone方法的逻辑就走完了。
2.5 cancel方法
在这里先介绍一下cancel方法
可以看到,在cancel方法中中断了任务执行的线程。
注意:这里只是给线程打上了一个中断的标记,具体任务执行是否结束,需要大家自己在任务里面添加响应中断的逻辑。(如果不添加任何判断中断的逻辑,那么cancel方法其实是没有任何效果的)
最后会执行finishCompletion方法:这个方法会将队列中的节点挂起的线程全部唤醒(别挂起了,快检查一下自己的任务执行完了没!!!),后续会细讲。
2.6 set方法
在上述awaitDone方法的标签6和标签7中,我们看到线程被挂起了,那么什么时候会被唤醒呢?
在上一步的cancel方法中,就会唤醒,奥秘就在finishCompletion方法中。
但是,除了cancel方法,还有一个关键的方法会执行唤醒操作,这个地方就是正常执行完并出结果的方法了。
这个run方法在上面已经介绍过了,接下来我们进入到这个set方法中详细看一下。
可以看到,这里也执行了finishCompletion方法!!!
2.7 finishCompletion方法
让我们进入到finishCompletion方法中一探究竟。
这里可以看到,将等待队列中的节点,通过for循环一个一个拿到,并将对应的线程唤醒了(别挂起了!!!结果出来了!!!)。
至此就和上面的awaitDone方法的标签6和标签7对应上了。
2.8 report方法
最后进入到report方法中了,来看一下最后的一步。
正常返回:将结果直接返回
被取消:抛出一个取消异常
异常返回:将异常对象封装到ExecutionException异常里面抛出
至此,我们的结果也就返回了。
2.9 回顾
最后让我们来回顾最开始的疑问:
1.为什么get方法能获取到任务的结果?
在FutureTask的awaitDone方法中会循环获取任务的执行状态,当发现任务的状态已完成时,就会将结果进行处理后返回。
2.cancel方法是怎么样尝试取消任务的?
在cancel时,会将执行任务的线程打上中断的标记,这里需要用户在任务中添加响应中断的逻辑,否则执行cancel方法时没有任务效果的。
3.通过get方法为什么能捕获到内部线程执行任务时抛出的异常,是怎么样实现的?
在FutureTask的run方法中,执行的时候会通过try catch捕获到内部线程执行的异常,并将异常信息赋值到成员变量outcome中,最后将outcome强转成异常封装到ExecutionException中返回。
标签:分析,get,任务,源码,线程,FutureTask,执行,方法 From: https://blog.csdn.net/qq_57102164/article/details/143528608