FutureTask底层实现
FutureTask在构建时,需要基于有参构造将Callable任务传递到Future中,在给线程提交任务时,提交的是FutureTask,不过Thread对象,值提供了传递Runnable(任务)的有参构造。> set方法第一步是基于CAS的方式,将任务状态从NEW,修改为COMPLETING,修改成功后,就会将返回的异常结果赋值给outcome。在任务完成后,无论是正常的完成,还是异常完成,还
一、FutureTask的基本使用
平时一些业务需要做并行处理,正常如果你没有返回结果的需求,直接上Runnable。
很多时候咱们是需要开启一个新的线程执行任务后,给我一个返回结果。此时咱们需要使用Callable。
在使用Callable的时候,一般就会配合FutureTask去使用。
FutureTask在构建时,需要基于有参构造将Callable任务传递到Future中,在给线程提交任务时,提交的是FutureTask,不过Thread对象,值提供了传递Runnable(任务)的有参构造。紧接着查看FutureTask的结构,会发现FutureTask实现了RunnableFuture的接口,RunnableFuture继承了Runnable。所以本质上来说,FutureTask也是Runnable类型。
基本使用方式
public static void main(String[] args) {
// 封装一个Callable的任务,扔到FutureTask中
Callable callable = new Callable() {
@Override
public Object call() throws Exception {
Thread.sleep(3000);
// 异常结束
// int i = 1 / 0;
double b = Math.random();
return b;
}
};
FutureTask task = new FutureTask(callable);
// 构建线程,并且传递Callable的任务
Thread t = new Thread(task);
// 启动线程
t.start();
// 主线程获取子线程中callable的任务结果
try {
Object o = task.get();
System.out.println("任务执行没异常。" + o);
} catch (Exception e) {
System.out.println("任务执行有异常。" + e);
}
}
二、FutureTask任务状态的流转
FutureTask中,提供了很多种任务状态
```java
private volatile int state;
private static final int NEW = 0; // 刚new任务
private static final int COMPLETING = 1; // 任务开始跑!
private static final int NORMAL = 2; // 正常结束,返回结果
private static final int EXCEPTIONAL = 3; // 异常结束,返回异常
private static final int CANCELLED = 4; // 任务取消,需要自己调用FutureTask提供的API
private static final int INTERRUPTING = 5; // 任务中断ing,根据线程走中断。。
private static final int INTERRUPTED = 6; // 任务中断了。
```
FutureTask任务的流转过程有这四种可能:
```java
NEW -> COMPLETING -> NORMAL
NEW -> COMPLETING -> EXCEPTIONAL
NEW -> CANCELLED
NEW -> INTERRUPTING -> INTERRUPTED
```
了解了这几个状态和状态流转的过程后,再查看源码中的几个核心属性
/** 传递给FutureTask的Callable,存这! */
private Callable<V> callable;
/** 存储返回结果的,正常返回和异常返回信息都存这。 */
private Object outcome;
/** 运行任务滴内个线程 执行任务的子线程*/
private volatile Thread runner;
/** 这里是排队等待结果的线程存储位置 等到结果的主线程*/
private volatile WaitNode waiters;
三、FutureTask中任务的执行
因为需要启动线程来执行FutureTask提供的任务。
而启动线程就是走Thread对象的start方法。
start方法就会调用有参构造传入的Runnable的run方法。
FutureTask是Runnable的实现类,自然也需要重写run方法。
就是查看FutureTask的run方法。
---
四、FutureTask中返回结果的封装
因为FutureTask的任务可以正常返回,也有异常返回。
在正常返回时,执行set方法。
> set方法第一步是基于CAS的方式,将任务状态从NEW,修改为COMPLETING,修改成功后,就会将返回结果赋值给outcome。最后再将状态从COMPLETING修改为NORMAL,代表任务正常结束了。
在异常返回时,执行setException方法
> set方法第一步是基于CAS的方式,将任务状态从NEW,修改为COMPLETING,修改成功后,就会将返回的异常结果赋值给outcome。最后再将状态从COMPLETING修改为EXCEPTIONAL,代表任务异常结束。
**线程池执行任务时,如果任务出现异常,会发生什么?**
* 线程结束的方式。 run方法结束(正常结束、异常结束)
* Runnable和FutureTask处理异常的方式。
* Runnable的异常会直接抛出来给线程
* FutureTask的异常会基于setException存储到outcome中。
五、FutureTask中任务取消的操作
cancel就是FutureTask提供的一个取消任务的方法。
这个方法中有一个参数,mayInterruptIfRunning。
如果传递true,会将执行任务的线程做interrupt方法,但是这个执行任务的线程能否及时停住,不一定,要看具体业务代码怎么写的。
如果传递false,不会中断,只会尝试修改状态
查看源码:
六、FutureTask获取结果的方式
其他线程要获取FutureTask的返回结果时,会执行get方法。
在get方法中,首先就是查看当前任务的状态是否完成,如果没完成,需要挂起当前等待任务的线程。
任务没完整,需要走awaitDone的逻辑。
在死循环中: **是一堆if else,每次循环只走一个逻辑。**
* 查看任务状态是否完成,完成就退出awaitDone逻辑。
* 如果任务状态是COMPLETING,那就yield稍等一会,因为任务马上完成!
* 查看当前线程是否被中断,如果被中断,最好后续处理,然后直接抛异常!
* 封装WaitNode
* 如果使用get方法时,指定了等待时间,先查看时间到了没,到了就不等了,返回。
* 正常将当前线程封装为WaitNode。
* 如果WaitNode还没添加到队列排队,这里就将声明好的WaitNode排队进来。
* 如果get方法指定了等待时间,在这里计算好等待的时间,并且任务没处理完,直接park(时间)挂起
* 前面都没走,最后在这,直接park无限期挂起,等待被唤醒。
最后会根据任务完成的状态做不同的操作。
* NORMAL:正常返回结果
* EXCEPTIONAL:将setException中存储的异常直接抛出。
* 大于CANCELLED:直接抛出CancellationException异常
七、FutureTask任务完成后的唤醒操作
在任务完成后,无论是正常的完成,还是异常完成,还是说被取消的情况,最终都会执行finishCompletion方法,去唤醒所有排队的WaitNode节点。
执行finishCompletion的第一步操作,就是拿到当前正在排队的waiters这个单向链表。
需要基于CAS将单向链表的waiters的引用置位null(help GC)。这里是个死循环,如果第一次CAS失败,会再次CAS。
然后将waiters中的单向链表的所有WaitNode中的线程统统滴unpark唤醒。
同时FutureTask也提供了一个done操作,可以在任务完成后,做一个你希望做的事,这个方法需要你自己重写
八、FutureTask的实战应用
下回分解
更多推荐
所有评论(0)