「本文已参与好文召集令活动,点击查看:后端、大前端双赛道投稿,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
方法中实现的第一种的步骤。
本文转载自: 掘金