不会用Java Future,我怀疑你泡茶没我快, 又是超长图文!! (4)

不会用Java Future,我怀疑你泡茶没我快, 又是超长图文!!

FutureTask 对象被创建出来,state 的状态就是 NEW 状态,从上面的构造函数中你应该已经发现了,四个最终状态 NORMAL ,EXCEPTIONAL , CANCELLED , INTERRUPTED 也都很好理解,两个中间状态稍稍有点让人困惑:

COMPLETING: outcome 正在被set 值的时候

INTERRUPTING:通过 cancel(true) 方法正在中断线程的时候

总的来说,这两个中间状态都表示一种瞬时状态,我们将几种状态图形化展示一下:

不会用Java Future,我怀疑你泡茶没我快, 又是超长图文!!

我们知道了 run() 方法是如何保存结果的,以及知道了将正常结果/异常结果保存到了 outcome 变量里,那就需要看一下 FutureTask 是如何通过 get() 方法获取结果的:

public V get() throws InterruptedException, ExecutionException { int s = state; // 如果 state 还没到 set outcome 结果的时候,则调用 awaitDone() 方法阻塞自己 if (s <= COMPLETING) s = awaitDone(false, 0L); // 返回结果 return report(s); }

awaitDone 方法是 FutureTask 最核心的一个方法

// get 方法支持超时限制,如果没有传入超时时间,则接受的参数是 false 和 0L // 有等待就会有队列排队或者可响应中断,从方法签名上看有 InterruptedException,说明该方法这是可以被中断的 private int awaitDone(boolean timed, long nanos) throws InterruptedException { // 计算等待截止时间 final long deadline = timed ? System.nanoTime() + nanos : 0L; WaitNode q = null; boolean queued = false; for (;;) { // 如果当前线程被中断,如果是,则在等待对立中删除该节点,并抛出 InterruptedException if (Thread.interrupted()) { removeWaiter(q); throw new InterruptedException(); } int s = state; // 状态大于 COMPLETING 说明已经达到某个最终状态(正常结束/异常结束/取消) // 把 thread 只为空,并返回结果 if (s > COMPLETING) { if (q != null) q.thread = null; return s; } // 如果是COMPLETING 状态(中间状态),表示任务已结束,但 outcome 赋值还没结束,这时主动让出执行权,让其他线程优先执行(只是发出这个信号,至于是否别的线程执行一定会执行可是不一定的) else if (s == COMPLETING) // cannot time out yet Thread.yield(); // 等待节点为空 else if (q == null) // 将当前线程构造节点 q = new WaitNode(); // 如果还没有入队列,则把当前节点加入waiters首节点并替换原来waiters else if (!queued) queued = UNSAFE.compareAndSwapObject(this, waitersOffset, q.next = waiters, q); // 如果设置超时时间 else if (timed) { nanos = deadline - System.nanoTime(); // 时间到,则不再等待结果 if (nanos <= 0L) { removeWaiter(q); return state; } // 阻塞等待特定时间 LockSupport.parkNanos(this, nanos); } else // 挂起当前线程,知道被其他线程唤醒 LockSupport.park(this); } }

总的来说,进入这个方法,通常会经历三轮循环

第一轮for循环,执行的逻辑是 q == null, 这时候会新建一个节点 q, 第一轮循环结束。

第二轮for循环,执行的逻辑是 !queue,这个时候会把第一轮循环中生成的节点的 next 指针指向waiters,然后CAS的把节点q 替换waiters, 也就是把新生成的节点添加到waiters 中的首节点。如果替换成功,queued=true。第二轮循环结束。

第三轮for循环,进行阻塞等待。要么阻塞特定时间,要么一直阻塞知道被其他线程唤醒。

对于第二轮循环,大家可能稍稍有点迷糊,我们前面说过,有阻塞,就会排队,有排队自然就有队列,FutureTask 内部同样维护了一个队列

/** Treiber stack of waiting threads */ private volatile WaitNode waiters;

说是等待队列,其实就是一个 Treiber 类型 stack,既然是 stack, 那就像手枪的弹夹一样(脑补一下子弹放入弹夹的情形),后进先出,所以刚刚说的第二轮循环,会把新生成的节点添加到 waiters stack 的首节点

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpgjwx.html