一、Futre介绍
主要特点:
1、可以拿到子线程的返回值
2、如果主线程要拿子线程的返回值,主线程会阻塞住
创建线程的方式一般常用的是 Thread、Runnable 的方式,如果需要当前处理的任务有返回值的话,需要使用 Callable,Callable需要配合 Future使用。
Future是一个接口,一般会使用 FutureTask实现类去接收 Callable任务的返回结果。
FutureTask存在一些问题,同步非阻塞执行的任务,他不会主动通知你返回结果是什么?
二、FutureTask的使用
简单的使用方式
- Callable 是你要执行的任务
- FutureTask 是存放任务返回的结果 --> 同步阻塞式的
csharp
public static void main(String[] args) throws Exception {
futrueTask();
}
// 测试FutureTask的使用
public static void futrueTask() throws Exception {
// 里面的lambda表达式 是 Callabe的实现
FutureTask<Integer> futureTask = new FutureTask<>(() -> {
System.out.println("我是FutureTask,我先执行!!");
Thread.sleep(2000);
return 110;
});
Thread thread = new Thread(futureTask);
System.out.println("我是主线程!!!");
thread.start();
System.out.println("阻塞住,等子任务跑完,才会拿到返回结果 : " + futureTask.get());
}
csharp
public static void callableTask() throws Exception {
// 测试使用 Collable
Callable<Integer> callable = () -> {
Thread.sleep(2000);
System.out.println("我是 Callable,阻塞了 2秒");
return 121;
};
FutureTask<Integer> futureTask = new FutureTask(callable);
Thread thread = new Thread(futureTask);
System.out.println("测试 Callable!!!!");
thread.start();
System.out.println("callable阻塞后的结果是:" + futureTask.get());
}
这块声明一点:
1、为什么 new Thread的时候,参数可以传FutureTask类型?

FutureTask实现了RunnableFuture接口,RunnableFuture接口继承了Runnable接口,所以FutureTask可以说是 Runnable接口的实现类
2、查看 FutureTask的构造器

参数可以传 Callable的实现类,也可以传 Runnable的实现类 + 结果 --> Runnable的实现类不需要指定返回结果,所以需要指定返回结果。
三、FutureTask源码分析
FutureTask的功能只分析如下几个方法:
1、构造器相关方法
2、run() --> 线程启动时,调用的方法
3、get() --> 主线程阻塞获取返回结果
余下的方法,根据这几个方法往下延伸分析。
1、属性分析
arduino
/**
*
* 状态流转过程
* NEW -> COMPLETING -> NORMAL
* NEW -> COMPLETING -> EXCEPTIONAL
* NEW -> CANCELLED
* NEW -> INTERRUPTING -> INTERRUPTED
*/
private volatile int state; // 表示子线程的运行状态,包含如下七个状态
private static final int NEW = 0; // 初始状态,构造器new的时候初始化为0
private static final int COMPLETING = 1; // 任务执行完,生成返回结果,封装给 FutureTask
private static final int NORMAL = 2; // 整个子线程运行结束,FutureTask封装结束
private static final int EXCEPTIONAL = 3; // 执行任务时,发生异常
private static final int CANCELLED = 4; // 任务被取消
private static final int INTERRUPTING = 5; // 线程的中断状态被设置为true(现在还在运行)
private static final int INTERRUPTED = 6; // 线程状态为中断
/** 任务封装为 Callable,具体还得交给 Callable来运行 */
private Callable<V> callable;
/** 返回结果,FutureTask.get() 拿到的返回结果 */
private Object outcome;
/** 具体执行的线程 */
private volatile Thread runner;
/** 如果有其他线程调用get()方法,需要挂起等待,这个单项链表用来存放需要挂起等待的线程 */
private volatile WaitNode waiters;
2、构造器分析
php
// 这里简单分析参数传递 Callable 的实现类的情况
public FutureTask(Callable<V> callable) {
if (callable == null){
throw new NullPointerException();
}
this.callable = callable; // 赋值给成员变量参数 Callable
this.state = NEW; // 初始化 FutureTask的状态为 New
}
3、run()方法
Thread.start() --> 启动线程之后,会调用到run()方法 --> Callable的 call方法,是通过run方法来执行的call方法
csharp
public void run() {
// 检查任务状态是否已经不是NEW(初始状态)
// 或者 CAS操作:尝试将runner字段从null设置为当前线程 ,如果操作失败,return
if (state != NEW || !RUNNER.compareAndSet(this, null, Thread.currentThread())){
return;
}
// 准备执行任务
try {
Callable<V> c = callable;
// 任务不为 null 并且 状态为初始化状态
if (c != null && state == NEW) {
// 放返回结果
V result;
// 任务执行是否为正常结束
boolean ran;
try {
// 运行call方法,拿到返回结果封装到result中
result = c.call();
// 任务正常结束
ran = true;
} catch (Throwable ex) { // 出现异常
result = null; // 结果为 null
ran = false; // 异常返回,ran设置为false
setException(ex); // 设置异常信息
}
if (ran){
// 正常返回结束,封装返回结果
set(result);
}
}
} finally {
// 将具体执行的线程置为 null
runner = null;
// 拿到状态
int s = state;
// 中断做一些后续处理
if (s >= INTERRUPTING){
handlePossibleCancellationInterrupt(s);
}
}
}
中断处理流程
csharp
// 中断处理流程
private void handlePossibleCancellationInterrupt(int s) {
//
if (s == INTERRUPTING)
// 终态是INTERRUPTED ,这里判断如果是中断设置为 true,则让出 CPU 的时间片
while (state == INTERRUPTING){
Thread.yield(); // wait out pending interrupt
}
// assert state == INTERRUPTED;
}
这里用自旋等待,不需要sleep的原因:
- 从 INTERRUPTING 到 INTERRUPTED 速度极快,几纳秒即可,用yield让出 CPU 时间片,不需要sleep的上下文线程切换的开销
vbnet
时间线:
t0: 状态=NEW, 任务运行中
t1: 线程A调用cancel(true)
状态变为INTERRUPTING
向运行线程发送中断信号
t2: 线程B调用get(),看到状态是INTERRUPTING
进入handlePossibleCancellationInterrupt
开始while循环
t3: 线程A完成中断,状态变为INTERRUPTED
t4: 线程B退出循环,继续执行
set方法
kotlin
protected void set(V v) {
// 通过 CAS 设置线程状态为completing
if (STATE.compareAndSet(this, NEW, COMPLETING)) {
// 结果赋值给 outcome
outcome = v;
// 设置状态为终态 NORMAL
STATE.setRelease(this, NORMAL); // final state
// 最终处理 --> 唤醒waiters单向链表里的阻塞线程
finishCompletion();
}
}
设置状态为 Normal终态 --> 赋值结果给成员属性 outcome --> 唤醒waiters里的阻塞线程
4、finishCompletion()方法
任务状态已经变为了NORMAL,做一些后续处理
java
// 从waiters单向链表里取值依次唤醒线程
private void finishCompletion() {
// assert state > COMPLETING;
// 循环遍历取waiters里的节点
for (WaitNode q; (q = waiters) != null;) {
if (WAITERS.weakCompareAndSet(this, q, null)) { // 将成员属性waiters里的值置为null
for (;;) { // 死循环从链表里取值
// 取出节点里的线程值
Thread t = q.thread;
if (t != null) {
// 移除掉线程值
q.thread = null;
// 唤醒线程
LockSupport.unpark(t);
}
// 往后找,接着唤醒,记录next节点
WaitNode next = q.next;
if (next == null){ // 如果没有next节点,直接break,说明队列里没有阻塞的线程了
break;
}
// 删除q节点
q.next = null; // unlink to help gc
// q来到next
q = next;
}
break;
}
}
// 拓展方法,没有任何实现,可自己实现
done();
// 成员属性 Callable 设置为null,任务处理完成了,可以拜拜了
callable = null; // to reduce footprint
}
核心流程:唤醒阻塞线程 + 成员属性 callable置为null
5、get()方法
其他线程调用get()方法,阻塞来获取 FutureTask的结果
get()方法有两个:一个带时间,一个不带时间

最终都会调用到awaitDone()这个方法,我们就从简单的get方法来进行分析
scss
public V get() throws InterruptedException, ExecutionException {
// 拿到当前 FutureTask的状态
int s = state;
// 满足这个状态,说明可能还没有返回结果
if (s <= COMPLETING){
// 没到completing,进行阻塞,挂起线程,等待拿结果
s = awaitDone(false, 0L);
}
// 拿到返回结果,想要拿到正常的返回结果,传进去的s参数得为 normal
return report(s);
}
6、awaitDone()方法
这个方法,主要是其他线程调用,不是当前 FutureTask调用,不要搞混了
java
/** 挂起线程,返回结果是int类型
* 线程等待任务执行结束,等待任务执行的状态变为大于 Completing状态
* timed参数:boolean值,是否传递时间参数
* nanos: 时间戳
*/
private int awaitDone(boolean timed, long nanos)throws InterruptedException {
// 开始时间,用于计算超时
long startTime = 0L;
// 等待节点
WaitNode q = null;
// 节点是否已入队列
boolean queued = false;
// 自旋循环
for (;;) {
int s = state;
if (s > COMPLETING) { // FutureTask的状态大于CANCELLED说明任务已经执行结束了
if (q != null){ // 如果设置过waitNode,将thread值置为 null
q.thread = null;
}
return s; // 返回状态值
}else if (s == COMPLETING){ // 如果任务的状态处于completing
// 让出CPU时间片 , completing的持续时间非常短,只需要让出cpu时间片即可,不需要睡眠
Thread.yield();
}else if (Thread.interrupted()) { // 判断当前线程是否中断
// 已经中断,将当前节点从waiters中移除
removeWaiter(q);
// 抛出中断异常
throw new InterruptedException();
}else if (q == null) { // 节点为 null
if (timed && nanos <= 0L){
// 设置了时间 并且 等待时间也到期了,直接返回
return s;
}
// 时间没到,new一个waitNode节点出来
q = new WaitNode();
}else if (!queued){ // 节点没入队列
// 通过 CAS ,用头插法 链接上waiters,并且将waiters的值设置为 q
queued = WAITERS.weakCompareAndSet(this, q.next = waiters, q);
}else if (timed) { // 根据时间来获取结果
final long parkNanos;
if (startTime == 0L) { // 头一次进来,设置startTime,取系统时间
startTime = System.nanoTime();
if (startTime == 0L){
startTime = 1L;
}
parkNanos = nanos;
} else {
// 第二次进来,计算跟头一次进来的差值
long elapsed = System.nanoTime() - startTime;
if (elapsed >= nanos) { // 判断差值是不是大于等于传参进来的值
removeWaiter(q); // 移除掉阻塞节点
return state; // 返回状态结果
}
// 第二次进来还没到等待的时间,更新等待的时间结果
parkNanos = nanos - elapsed;
}
if (state < COMPLETING){ // 第二次进来,时间没到,并且FutureTask的任务还没到 Completing状态
// 指定时间挂起线程
LockSupport.parkNanos(this, parkNanos);
}
}else {
// 挂起线程
LockSupport.park(this);
}
}
}
// 内部类节点
static final class WaitNode {
volatile Thread thread;
volatile WaitNode next;
// 初始化thread为当前线程
WaitNode() { thread = Thread.currentThread(); }
}
这里只是涉及到挂起,等run()方法执行完毕,会唤醒waiters里的所有节点
7、report()方法
java
private V report(int s) throws ExecutionException {
Object x = outcome;
if (s == NORMAL){ // 判断是否是normal状态
// 说明正常结束,返回结果
return (V)x;
}
// 非正常结束 cancelled / 中断 报异常
if (s >= CANCELLED){
throw new CancellationException();
}
// 异常结束
throw new ExecutionException((Throwable)x);
}
四、总结
1、当前 FutureTask方法具体会执行run()方法 --> 封装了 Callable --> 具体执行的是callable的call方法 --> 唤醒其他阻塞线程
2、其他线程调用get()方法 --> 会扔到watiers单向链表等待队列里,两者配合