「本文已参与好文召集令活动,点击查看:后端、大前端双赛道投稿,2万元奖池等你挑战!」
前言
说到FutureTask就不得不说到Callabl和Future;其中Callabl是一个接口,用来定义任务,且有返回值的地方,且可以有返回值。Future是用来获取Callabl执行结果的。本篇笔记主要写FutureTask源码的。
正文
1 | java复制代码 |
FutureTask实现了RunnableFuture接口。而RunnableFuture接口又继承了 Runnable接口和Future接口。
所以,FutureTask可以作为Runnable参数,传入Thread,然后启动线程执行任务。
常量
1 | java复制代码 |
在FutureTask中定义了一个state,标记任务的执行状态。
1 | java复制代码 |
其中状态之间的转换也在注释中已经说明。
构造方法
1 | java复制代码 |
构造方法有两个,都是将状态置为NEW;
其中接收Runnable参数的构造方法需要传入固定值,且使用Executors.callable方法将Runnable和result转为callable,该方法中用到了适配器模式。感兴趣的可以进源码去看看。
run方法
run方法是线程直接调用的地方。其实最根本的原理和Runnable的执行是一样的,只是这里多进行了一次包装,对执行结果进行了处理。
1 | java复制代码 |
run方法看起来还是比较简单,就是执行call方法,然后保存执行结果。
这里主要看两个方法,setException和set。
setException
这个方法是执行任务出错后调用的方法,其中将错误信息作为参数。下面来看看源码:
1 | java复制代码 |
源码也比较简单:
CAS将state从NEW转为COMPLETING- 将错误信息赋予
outcome - 将
state转为EXCEPTIONAL - 执行
finishCompletion方法
set方法
这个方法是执行任务成功后调用的方法,将执行结果作为参数传入该方法。源码如下:
1 | java复制代码 |
流程和setException方法是一样的,唯一的区别就是第三步是将state转换为NORMAL。
finishCompletion
这个方法是在set和setException中都调用了的。注释说明的是:移除所有等待线程,并向线程发送信号,执行done()方法,置callable为null;
下面看看源码:
1 | java复制代码 |
源码也比较简单,就是for循环遍历等待线程的链表,然后通过LockSupport.unpark来通知线程。这里主要注意LockSupport.unpark的使用和原理。
其实线程执行任务的大概流程就是:
- 线程执行任务
- 判断执行结果,并保存结果(执行失败还是成功)
- 修改状态和保存结果信息
- 挨个通知等待中的线程(
LockSupport.unpark)
isCancelled
根据状态判断是否被取消掉
1 | java复制代码 |
isDone
判断线程是否完成
1 | java复制代码 |
cancel
取消任务
源码如下:
1 | java复制代码 |
源码也比较简单,主要还是看mayInterruptIfRunning参数,这个参数就是是否取消正在执行中的任务。为true的话就使用Thread的interrupt来取消任务的执行。最后通知等待线程取消等待。
get
这个名字有两个方法,一个是一直等待,一个是等待有限时长。
1 | java复制代码 |
大概流程就是:
- 判断状态,是否调用
awaitDone - 根据状态返回结果
两个方法都差不多,都是调用awaitDone方法,只是参数不一样。
awaitDone
1 | java复制代码 |
这个方法主要就三点:
- 用当前线程构建
WaitNode,然后使用CAS将WaitNode加入waiters中,waiters就是一个链表 - 使用
LockSupport来阻塞线程(有限时长或无限时长) - 成功则返回状态码
report
这个方法也比较简单
1 | java复制代码 |
最后
总结
整体来说,源代码是比较简单的。思想上来说,FutureTask就是一个中间类,实质可以看成是一个Runnable,其内部对Callable进行了包装(重写run方法),然后使用LockSupport来对线程阻塞和通知线程,使其达到等待任务执行完成,且获取结果的效果。
FutureTask中的state(状态),从执行任务的角度(callable)来看,严格意义上来说状态并没有完全一一对应,比如NEW状态,有可能任务已经完成,或者执行出错(只是结果还未保存到共享变量(outcome)中),这一点可以在run方法中看出,状态的修改是在Callable的call方法执行过后。如果FutureTask整体的角度来看就不一样了。
Callable的启动有两种方式:
第一种:将Callable作为参数,构建FutureTask,然后将FutureTask作为参数,创建Thread实例,然后启动start。
第二种:直接将Callable放入线程池的submit方法中,然后返回FutureTask。线程池的原理和第一种方式是一样的,线程池是在submit方法中实现的第一种的步骤。
本文转载自: 掘金