文章目录
1.参数说明
1.任务状态
1.新创建状态:NEW
private static final int NEW = 0;
2.完成中但还没完成:COMPLETING
private static final int COMPLETING = 1;
3.正常状态:NORMAL
private static final int NORMAL = 2;
4.异常状态:EXCEPTIONAL
private static final int EXCEPTIONAL = 3;
5.取消状态:CANCELLED
private static final int CANCELLED = 4;
6.中断中状态:INTERRUPTING
private static final int INTERRUPTING = 5;
7.已经中断状态:INTERRUPTED
private static final int INTERRUPTED = 6;
8.状态切换
参照给出的注释得到下面状态切换的几种情况:
/**
* Possible state transitions:
* NEW -> COMPLETING -> NORMAL
* NEW -> COMPLETING -> EXCEPTIONAL
* NEW -> CANCELLED
* NEW -> INTERRUPTING -> INTERRUPTED
*/
2.等待的节点:WaiterNode
可以看到注释这边说的是使用Treiber Stack的方式来实现,即无锁并发栈,他的基本思想也是通过CAS,后面我们会看到,这边先了解一下这个数据结构,WaitNode,记录对应的等待线程和下一个等待的节点.
在FutureTask中维护了一个WaiterNode即private volatile WaitNode waiters;
这边的waiter其实对应的就是FutureTask的get操作,因为任务可能还没处理完, get得不到结果,可能多次get,所以需要对这些get操作进行阻塞,等待真正的任务处理结束之后,再回到队列中去处理get的操作
/**
* Simple linked list nodes to record waiting threads in a Treiber
* stack. See other classes such as Phaser and SynchronousQueue
* for more detailed explanation.
*/
static final class WaitNode {
volatile Thread thread;
volatile WaitNode next;
WaitNode() { thread = Thread.currentThread(); }
}
3.返回结果或者返回异常:outcome
后面我们会具体说到这个,这边先了解一下,可以设置返回结果(在构造函数中设置),如果一切正常的话,按照设置的结果返回,如果发生异常则会被设置为对应的异常实例。
outcome在本地可以说算是一个缓存,当任务运行过程中,如果调用get方法,会一直等待任务结束,然后返回outcome的结果,像我们刚才说的两种情况
1.一切正常返回构造时设置的结果
2.发生异常则返回对应异常实例
/** The result to return or exception to throw from get() */
private Object outcome; // non-volatile, protected by state reads/writes
4.任务回调接口:Callable
/**
* A task that returns a result and may throw an exception.
* Implementors define a single method with no arguments called
**/
/** The underlying callable; nulled out after running */
private Callable<V> callable;
5.任务线程:runner
/** The thread running the callable; CASed during run() */
private volatile Thread runner;
2.构造函数
1.Callable入参
通过传入Callable的实现类来构造FutureTask
public FutureTask(Callable<V> callable) {
if (callable == null){
throw new NullPointerException();
}
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
2.Runnable入参
参考注释,如果不需要指定result,可以传null。这个构造方法会把Runnable的入参包装成Callable的具体实现类,这边使用的是RunnableAdapter这个实现类
/**
* Creates a {@code FutureTask} that will, upon running, execute the
* given {@code Runnable}, and arrange that {@code get} will return the
* given result on successful completion.
*
* @param runnable the runnable task
* @param result the result to return on successful completion. If
* you don't need a particular result, consider using
* constructions of the form:
* {@code Future<?> f = new FutureTask<Void>(runnable, null)}
* @throws NullPointerException if the runnable is null
*/
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW; // ensure visibility of callable
}
public static <T> Callable<T> callable(Runnable task, T result) {
if (task == null)
throw new NullPointerException();
return new RunnableAdapter<T>(task, result);
}
RunnableAdapter支持传入result,就是我们上面说的outCome怎么返回的问题,这边不赘述
static final class RunnableAdapter<T> implements Callable<T> {
final Runnable task;
final T result;
RunnableAdapter(Runnable task, T result) {
this.task = task;
this.result = result;
}
public T call() {
task.run();
return result;
}
}
3.方法实现
1.线程执行:run
2.获取运行结果:get
如果当前的FutureTask是NEW或者COMPLETING状态,也就是还没有到最终状态的时候,需要把get操作等待在wait节点的链表中。
当前任务结束之后,返回结果,即report,report可能返回对应的result,也可能是抛出错误。
public V get() throws InterruptedException, ExecutionException {
int s = state;
//线程还在NEW或者COMPLETING状态则进行等待.
if (s <= COMPLETING) {
s = awaitDone(false, 0L);
}
//等待结束之后,返回结果
return report(s);
}
1.返回执行结果:report
private V report(int s) throws ExecutionException {
Object x = outcome;
if (s == NORMAL) {
//正常状态,返回之前设置的Result
return (V) x;
}
//如果是被中断或者取消,抛出错误
if (s >= CANCELLED) {
throw new CancellationException();
}
throw new ExecutionException((Throwable) x);
}
2.等待任务执行结束:awaitDown
要看一下awaitDown方法如果等待线程执行完任务。
这边的判断条件比较多(好几个else if…这代码写的不是很优雅啊)。我们先一步一步来看一下他的实现
1.刚开始先是根据timed是否计时来获取最终结束时间(如果有计时,没有计时的时候为0)
2.然后进入死循环
1.如果当前线程是中断状态,则把waiter移除队列,等下看一下这个移除操作,不仅仅是移除自己这个节点,移出之后抛出中断异常
下面这些判断都是在同一个ifelse的判断中所以是有关系的,这边把这些判断按照条件成立前后关系标注A1~n来阐述能更好说明问题.
A1.如果状态已经不是NEW和COMPLETING了,可以返回结果了,这边有一步是q.thread=null,这个操作是告诉后面的removeWaiter方法,这个waiter是可以移除的节点了。
A2.如果线程还是COMPLETING,则调用Thread.yield,进入就绪态,让出CPU重新抢占
A3.如果FutureTask是NEW的状态要新创建一个wait节点
A4.FutureTask还是NEW或者COMPLETING的状态并且waiter还没有进队列,因为不知道要等待多久,所以还是把waiter插入队列
A5.此时状态还是NEW或者COMPLETING,如果有计时在实现计时的处理,时间到了移除队列,时间没有到则挂起对应时间等待。
A6.最后是没有计时的情况,休眠等待被唤醒
private int awaitDone(boolean timed, long nanos)
throws InterruptedException {
//通过timed来判断是否计时和结束时间
final long deadline = timed ? System.nanoTime() + nanos : 0L;
WaitNode q = null;
boolean queued = false;
//进入死循环
for (; ; ) {
//线程被中断则要把当前的Wait节点移除掉
if (Thread.interrupted()) {
removeWaiter(q);
throw new InterruptedException();
}
int s = state;
//线程已经是COMPLETING之后的状态:正常、异常、中断
if (s > COMPLETING) {
//判断节点不为空,则把节点的线程置空,返回状态
if (q != null) {
//并发各种情况都有可能,所以这边也要清空一下Thread
q.thread = null;
}
return s;
}// cannot time out yet
//如果线程还是COMPLETING,则调用Thread.yield,进入就绪态,让出CPU重新抢占
else if (s == COMPLETING) {
Thread.yield();
}
//这边应该是 !s > COMPLETING && q!=null,就是说FutureTask还是NEW、或者COMPLETING的状态,谁也不知道他什么时候执行,我先进入wait queue等待一下。
else if (q == null) {
q = new WaitNode();
}
//这边应该是 !s >= COMPLETING && q!=null,就是说FutureTask还是NEW的状态,谁也不直到他什么时候执行,我先进入wait queue等待一下。
else if (!queued) {
//这边的处理是 q.next = waiters.也就是说后面进来的节点会成为FutureTask的waiter,通过这个waiter去查找后面的元素,是栈的实现
queued = UNSAFE.compareAndSwapObject(this, waitersOffset, q.next = waiters, q);
}
//这边就是 !s >= COMPLETING && q!=null && 加入队列,如果有计时,开始挂起计时
else if (timed) {
//时间到了则从节点中移除
nanos = deadline - System.nanoTime();
if (nanos <= 0L) {
removeWaiter(q);
return state;
}
//否则挂起对应时间
LockSupport.parkNanos(this, nanos);
} else {
//不满足上述情况进行挂起,当FutureTask的run运行结束之后判断waiter的Thread不为空则会进行唤醒
LockSupport.park(this);
}
}
}
3.移除waiter:removeWaiter
我们看到注释上面其实也有提到,这边是通过cas的方式来实现的无锁操作,因为这边会存在两个循环,在某些情况下可能从头开始处理,就是说可能你已经处理到第十个节点了,但是发现有问题就要从头再来,所以这边告诉你可能会有效率问题,因为处理的waiter节点是对应的get操作,人家也说了设计的目的考虑的是waiter是非常少量的,如果你蛇精病死循环去get,你还能怪人家?
这边方法一进来,先是把当前需要取消的节点的thread设置为null,我们在上面其实也有看到这样的操作,其实对于FutureTask来说,当你的waiter的thread为空的时候就需要被移除了,算是一个标记。
然后我们进入了第一个死循环,而第一个死循环结束的条件是第二个死循环不满足q != null这个条件才会结束。
我们来看一下第二个循环,这边存在三个判断:
1.先判断waiter.thread是否为空,如果不为空就要往下一个节点去查找了
2.不满足上述的判断即 当前节点的p.thread == null并且前一个节点不为空(这说明不是头结点),会先把当前节点p断开,然后再判断前一个节点pre的thread是不是为空,这个pre的thread按道理如果是第一次检查过了不会为空,但是你永远想象不到并发的情况下可能它对应的waiter也要取消,把它的thread置空了,那么这样就会存在一种极端情况就是执行到这一步的时候,pre这个节点被另一个removeWaiter的线程抛弃了,那么他的next就是null了,按道理这个时候应该直接break出去,因为该做的做完了,但是这个removeWaiter真是尽职尽责,他会跳会第一个循环重新再查找一边,保证所有的该清除的节点都清除掉
3.第三步则是比较特殊的情况就是满足q.thread == null && pre == null,即刚好要移除的就是第一个waiter,但是移除之后依然会回到一个死循环继续查找
直到最后,找到最后一个waiter(直到next == null)才退出死循环。
/**
* Tries to unlink a timed-out or interrupted wait node to avoid
* accumulating garbage. Internal nodes are simply unspliced
* without CAS since it is harmless if they are traversed anyway
* by releasers. To avoid effects of unsplicing from already
* removed nodes, the list is retraversed in case of an apparent
* race. This is slow when there are a lot of nodes, but we don't
* expect lists to be long enough to outweigh higher-overhead
* schemes.
*/
private void removeWaiter(WaitNode node) {
if (node != null) {
//清空Thread,把要移除的wait节点做一个标记
node.thread = null;
// restart on removeWaiter race
retry:
for (; ; ) {
//这边的结束条件是 q == null;
for (WaitNode pred = null, q = waiters, s; q != null; q = s) {
s = q.next;
//如果我们要移除的节点不是头结点,因为我们已经设置了Thread = null了.继续往下一个节点查找
if (q.thread != null) {
pred = q;
}
//这个地方显然是不满足第一个if才会进来,即 q.thread == null && pred != null
else if (pred != null) {
//把q节点断开了
pred.next = s;
// check for race
//这边再来一个判断是因为在多线程的情况下,可能在上面pred的thread也被另一个线程调用removeWaiter置为空了。
//所以这边返回头结点,继续从头开始查找处理
if (pred.thread == null) {
continue retry;
}
}
//不满足上面条件,即 q.thread == null && pre == null.就是说当前需要移出的元素在栈顶,然后把栈顶元素替换掉.
else if (!UNSAFE.compareAndSwapObject(this, waitersOffset, q, s)) {
continue retry;
}
}
break;
}
}
}
3.取消执行:cancel
cancel方法这边有一个入参是mayInterruptIfRunning,当这个入参为true的时候表示是中断取消的,如果是false则是其他原因。
这边如果不满足条件不会进行取消,这个条件是:任务的状态是NEW,如果是NEW,此时会通过CAS设置他的状态,设置成功才能进入取消,也可能在设置的过程中任务被启动了,就会失败,失败则直接返回
如果设置成功,这边只会对中断产生的cancel进行中断处理,否则直接进入finally中进行处理。finishCompletion方法表示唤醒所有等待的节点,因为任务已经被取消或者已经处理结束了(就是变成某些终止状态了,必须进行结束)。
//入参表示是中断还是取消节点,true表示中断,false表示取消
public boolean cancel(boolean mayInterruptIfRunning) {
//如果不满足当前条件的不能取消: 不满足 新创建的线程并且设置成对应取消状态,直接返回,也就是说以一旦进入运行是不能取消的,除非还在等待
if (!(state == NEW && UNSAFE.compareAndSwapInt(this, stateOffset, NEW, mayInterruptIfRunning ? INTERRUPTING : CANCELLED))){
return false;
}
// in case call to interrupt throws exception
try {
//如果是CANCELED只是在上面设置状态.然后在finally中进行处理
//如果是中断会在这边设置中断状态
if (mayInterruptIfRunning) {
try {
Thread t = runner;
if (t != null){
t.interrupt();
}
} finally { // final state
UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
}
}
} finally {
//结束处理,唤醒所有等待的wait节点
finishCompletion();
}
return true;
}

本文详细解析了FutureTask的源码,包括参数说明、任务状态转换、等待节点WaiterNode、返回结果和异常、Callable接口、任务线程runner的实现。介绍了构造函数的Callable和Runnable入参,以及run、get方法的执行流程,特别是get方法中的awaitDown过程,涉及到无锁并发栈WaiterNode的管理。同时讲解了cancel方法如何中断任务并唤醒等待线程。

1096

被折叠的 条评论
为什么被折叠?



