不就只剩下一个 EXCEPTIONAL 的情况了。所以,经过前面的描述,我们可以总结一下。当 FutureTask 的 status 为 NORMAL 时正常返回结果,当 status 为 EXCEPTIONAL 时抛出异常。而当终态为 NORMAL 或者 EXCEPTIONAL 时,按照注释描述,状态的流程只能是这样的:
这个 task 是一个 FutureTask,所以 run 方法其实是 FutureTask 的 run 方法。
跟着断点进去之后,就是 FutureTask 的 run 方法:
答案都藏在这个方法里面。
java.util.concurrent.FutureTask#run标号为 ① 的地方是执行我们的任务,call 的就是示例代码里面的 sayHi 方法。如果提交的任务( sayHi 方法)抛出的运行时异常没有被捕获,则会在标号为 ② 的这个 catch 里面被捕获。然后执行标号为 ② 的这个代码。如果提交的任务( sayHi 方法)捕获了运行时异常,则会进入标号为 ③ 的这个逻辑里面。我们分别看一下标号为 ② 和 ③ 的逻辑:
首先,两个方法都是先进行一个 cas 的操作,把当前 FutureTask 的 status 字段从 NEW 修改为 COMPLETING 。
完成了状态流转的这一步:
注意这里,如果 cas 操作失败了,则不会进行任何操作。
cas 操作失败了,说明什么呢?说明当前的状态是 CANCELLED 或者 INTERRUPTING 或者 INTERRUPTED。也就是这个任务被取消了或者被中断了。那还设置结果干啥,没有任何卵用,对不对。如果 cas 操作成功,接着往下看,可以看到虽然入参不一样了,但是都赋给了 outcome 变量,这个变量,在上一节的 report 方法出现过,还记得吗?能不能呼应上?接下来就是状态接着往下流转。set 方法表示正常结束,状态流转到 NORMAL。setException 方法表示任务出现异常,状态流转到 EXCEPTIONAL。所以经过 FutureTask 的 run 方法后,如果任务没有被中断或者取消,则会通过 setException 或者 set 方法完成状态的流转和 outcome 参数的设置:
而到底是调用 setException 方法还是 set 方法,取决于标号为 ① 的地方是否会抛出异常。
即取决于任务体是否会抛出异常。假设 sayHi 方法是这样的,会抛出运行时异常:
而通过 submit 方法提交任务时写法分别如下:
如果是标号为 ① 的写法,则会进入 setException 方法。如果是标号为 ② 的写法,则会进入 set 方法。所以,你现在再回去看看这个题目:当执行方法是 submit 的时候,如果子线程抛出未经捕获的运行时异常,将会被封装到 Future 里面,那么如果子线程捕获了异常,该异常还会封装到 Future 里面吗?是怎么实现的呢?现在是不是很清晰了。如果子线程捕获了异常,该异常不会被封装到 Future 里面。是通过 FutureTask 的 run 方法里面的 setException 和 set 方法实现的。在这两个方法里面完成了 FutureTask 里面的 outcome 变量的设置,同时完成了从 NEW 到 NORMAL 或者 EXCEPTIONAL 状态的流转。