download:马士兵-AI 人工智能工程师|完结无密内置文档资料
FutureTask源码深度分析
在JDK的FutureTask当中会运用到一个工具LockSupport,在正式引见FutureTask之前我们先熟习一下这个工具。
LockSupport主要是用于阻塞和唤醒线程的,它主要是经过包装UnSafe类,经过UnSafe类当中的办法停止完成的,他底层的办法是经过依赖JVM完成的。在LockSupport当中主要有以下三个办法:
unpark(Thread thread))办法,这个办法能够给线程thread发放一个答应证,你能够经过屡次调用这个办法给线程发放答应证,每次调用都会给线程发放一个答应证,但是这个答应证不可以停止累计,也就是说一个线程可以具有的最大的答应证的个数是1一个。
park()办法,这个线程会消费调用这个办法的线程一个答应证,由于线程的默许答应证的个数是0,假如调用一次那么答应证的数目就变成-1,当答应证的数目小于0的时分线程就会阻塞,因而假如线程历来没用调用unpark办法的话,那么在调用这个办法的时分会阻塞,假如线程在调用park办法之前,有线程调用unpark(thread)办法,给这个线程发放一个答应证的话,那么调用park办法就不会阻塞。
parkNanos(long nanos)办法,同park办法一样,nanos表示最长阻塞超时时间,超时后park办法将自动返回,假如调用这个办法的线程有答应证的话也不会阻塞。
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
public class Demo {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
LockSupport.park(); // 没有答应证 阻塞住这个线程
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("阻塞完成");
});
thread.start();
TimeUnit.SECONDS.sleep(2);
LockSupport.unpark(thread); //给线程 thread 发放一个答应证
System.out.println("线程启动");
}
}
复制代码
上面代码的执行结果
线程启动
阻塞完成
复制代码
从上面代码我们能够晓得LockSupport.park()能够阻塞一个线程,由于假如没有阻塞的话肯定会先打印阻塞完成,由于打印这句话的线程只休眠一秒,主线程休眠两秒。
在源代码当中你能够会遇到UNSAFE.compareAndSwapXXX的代码,这行代码主要是停止原子交流操作CAS,比方:
UNSAFE.compareAndSwapInt(this, stateOffset, NEW, CANCELLED)))
复制代码
上面的代码主要是将this对象当中的内存偏移地址为stateOffset的对象拿出来与NEW停止比拟,假如等于NEW那就将这个值设置为CANCELLED,这整个操作是原子的(由于可能多个线程同时调用这个函数,因而需求保证操作是原子的),假如操作胜利返回true反之返回false。假如你目前不是很了解也没关系,只需求晓得它是将对象this的内存偏移为stateOffset的值交换为CANCELLED就行,假如这个操作胜利返回true,不胜利返回false。
FutureTask回忆
我们首先来回忆一下FutureTask的编程步骤:
写一个类完成Callable接口。
@FunctionalInterface
public interface Callable
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
复制代码
完成接口就完成call即可,能够看到这个函数是有返回值的,而FutureTask返回给我们的值就是这个函数的返回值。
new一个FutureTask对象,并且new一个第一步写的类,new FutureTask<>(callable完成类)。
最后将刚刚得到的FutureTask对象传入Thread类当中,然后启动线程即可new Thread(futureTask).start();。
然后我们能够调用FutureTask的get办法得到返回的结果futureTask.get();。
可能你会对FutureTask的运用方式觉得困惑,或者不是很分明,如今我们来认真捋一下思绪。
首先启动一个线程要么是继承自Thread类,然后重写Thread类的run办法,要么是给Thread类传送一个完成了Runnable的类对象,当然能够用匿名内部类完成。
既然我们的FutureTask对象能够传送给Thread类,阐明FutureTask肯定是完成了Runnable接口,事实上也的确如此
能够发现的是FutureTask的确完成了Runnable接口,同时还完成了Future接口,这个Future接口主要提供了后面我们运用FutureTask的一系列函数比方get。
看到这里你应该可以大致想到在FutureTask中的run办法会调用Callable当中完成的call办法,然后将结果保管下来,当调用get办法的时分再将这个结果返回。
状态表示
首先我们先理解一下FutureTask的几种状态:
NEW,刚刚新建一个FutureTask对象。
COMPLETING,FutureTask正在执行。
NORMAL,FutureTask正常完毕。
EXCEPTIONAL,假如FutureTask对象在执行Callable完成类对象的call办法的时分呈现的异常,那么FutureTask的状态就变成这个状态了。
CANCELLED,表示FutureTask的执行过程被取消了。
INTERRUPTING,表示正在终止FutureTask对象的执行过程。
INTERRUPTED,表示FutureTask对象在执行的过程当中被中缀了。
这些状态之间的可能的转移状况如下所示:
NEW -> COMPLETING -> NORMAL。
NEW -> COMPLETING -> EXCEPTIONAL。
NEW -> CANCELLED。
NEW -> INTERRUPTING -> INTERRUPTED。
在FutureTask当中用数字去表示这几个状态:
private volatile int state;
private static final int NEW = 0;
private static final int COMPLETING = 1;
private static final int NORMAL = 2;
private static final int EXCEPTIONAL = 3;
private static final int CANCELLED = 4;
private static final int INTERRUPTING = 5;
private static final int INTERRUPTED = 6;
复制代码
中心函数和字段
FutureTask类当中的中心字段
private Callable
复制代码
private Object outcome; // 用于保管 Callable 当中 call 函数的返回结果
复制代码
private volatile Thread runner; // 表示正在执行 call 函数的线程
复制代码
private volatile WaitNode waiters;// 被 get 函数挂起的线程 是一个单向链表 waiters 表示单向链表的头节点
static final class WaitNode {
volatile Thread thread; // 表示被挂起来的线程
volatile WaitNode next; // 表示下一个节点
WaitNode() { thread = Thread.currentThread(); }
}
复制代码
结构函数:
public FutureTask(Callable
if (callable == null)
throw new NullPointerException();
this.callable = callable; //保管传入来的 Callable 接口的完成类对象
this.state = NEW; // 这个就是用来保管 FutureTask 的状态 初识时 是新建状态
}
复制代码
run办法,这个函数是完成Runnable接口的办法,也就是传入Thread类之后,Thread启动时执行的办法。
public void run() {
// 假如 futuretask 的状态 state 不是 NEW
// 或者不可以设置 runner 为当前的线程的话直接返回
// 不在执行下面的代码 由于 state 曾经不是NEW
// 阐明取消了 futuretask 的执行
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return;
try {
Callable c = callable;
if (c != null && state == NEW) {
V result;
boolean ran; // 这个值主要用于表示 call 函数能否正常执行完成 假如正常执行完成就为 true
try {
result = c.call(); // 执行 call 函数得到我们需求的返回值并且柏村在
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex); // call 函数异常执行 设置 state 为异常状态 并且唤醒由 get 函数阻塞的线程
}
if (ran)
set(result); // call 函数正常执行完成 将得到的结果 result 保管到 outcome 当中 并且唤醒被 get 函数阻塞的线程
}
} finally {
// runner must be non-null until state is settled to
// prevent concurrent calls to run()
runner = null;
// state must be re-read after nulling runner to prevent
// leaked interrupts
int s = state;
// 假如这个if语句条件满足的话就表示执行过程被中缀了
if (s >= INTERRUPTING)
// 这个主要是后续处置中缀的过程不是很重要
handlePossibleCancellationInterrupt(s);
}
}
复制代码
set办法,主要是用于设置state的状态,并且唤醒由get函数阻塞的线程。
protected void set(V v) { // call 办法正常执行完成执行下面的办法 v 是 call 办法返回的结果
// 这个是原子交流 state 从 NEW 状态变成 COMPLETING 状态
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = v; // 将 call 函数的返回结果保管到 outcome 当中 然后会在 get 函数当中运用 outcome
// 由于 get 函数需求得到 call 函数的结果 因而我们需求在 call 函数当中返回 outcome
// 下面代码是将 state 的状态变成 NORMAL 表示程序执行完成
UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
// 由于其他线程可能在调用 get 函数的时分 call 函数还没有执行完成 因而这些线程会被阻塞 下面的这个办法主要是将这些线程唤醒
finishCompletion();
}
}
protected void setException(Throwable t) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = t; // 将异常作为结果返回
// 将最后的状态设置为 EXCEPTIONAL
UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
finishCompletion();
}
}
复制代码
get办法,这个办法主要是从FutureTask当中取出数据,但是这个时分可能call函数还没有执行完成,因而这个办法可能会阻塞调用这个办法的线程。
public V get() throws InterruptedException, ExecutionException {
int s = state;
// 假如当前的线程还没有执行完成就需求将当前线程挂起
if (s <= COMPLETING)
// 调用 awaitDone 函数将当前的线程挂起
s = awaitDone(false, 0L);
// 假如 state 大于 COMPLETING 也就说是完成状态 能够直接调用这个函数返回
// 当然也能够是从 awaitDone 函数当中恢复执行才返回
return report(s);// report 办法的主要作用是将结果 call 函数的返回结果 返回进来 也就是将 outcome 返回
}
private V report(int s) throws ExecutionException {
Object x = outcome;
if (s == NORMAL) // 假如程序正常执行完成则直接返回结果
return (V)x;
if (s >= CANCELLED) // 假如 s 大于 CANCELLED 阐明程序要么是被取消要么是被中缀了 抛出异常
throw new CancellationException();
// 假如上面两种转台都不是那么阐明在执行 call 函数的时分程序发作异常
// 还记得我们在 setException 函数当中将异常赋值给了 outcome 吗?
// 在这里将那个异常抛出了
throw new ExecutionException((Throwable)x);
}
复制代码
awaitDone办法,这个办法主要是将当前线程挂起。
private int awaitDone(boolean timed, long nanos) // timed 表示能否超时阻塞 nanos 表示假如是超时阻塞的话 超时时间是几
throws InterruptedException {
final long deadline = timed ? System.nanoTime() + nanos : 0L;
WaitNode q = null;
boolean queued = false;
for (;;) { // 留意这是个死循环
// 假如线程被中缀了 那么需求从 “等候队列” 当中移进来
if (Thread.interrupted()) {
removeWaiter(q);
throw new InterruptedException();
}
int s = state;
// 假如 call 函数执行完成 留意执行完成可能是正常执行完成 也可能是异常 取消 中缀的任何一个状态
if (s > COMPLETING) {
if (q != null)
q.thread = null;
return s;
}
// 假如是正在执行的话 阐明马上就执行完成了 只差将 call 函数的执行结果赋值给 outcome 了
// 因而能够不停止阻塞先让出 CPU 让其它线程执行 可能下次调度到这个线程 state 的状态很可能就
// 变成 完成了
else if (s == COMPLETING) // cannot time out yet
Thread.yield();
else if (q == null)
q = new WaitNode();
else if (!queued) // 假如节点 q 还没有入队
// 下面这行代码略微有点复杂 其中 waiter 表示等候队列的头节点
// 这行代码的作用是将 q 节点的 next 指向 waiters 然后将 q 节点
// 赋值给 waiters 也就是说 q 节点变成等候队列的头节点 整个过程能够用
// 下面代码标识
// q.next = waiters;
// waiters = q;
// 但是将 q 节点赋值给 waiter这个操作是原子的 可能胜利也可能不胜利
// 假如不胜利 由于 for 循环是死循环下次喊 还会停止 if 判别
// 假如 call 函数曾经执行完成得到其返回结果那么就能够直接返回
// 假如还没有完毕 那么就会调用下方的两个 if 分支将线程挂起
queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
q.next = waiters, q);
else if (timed) {
// 假如是运用超时挂起 deadline 表示假如时间超越这个值的话就能够将线程启动了
nanos = deadline - System.nanoTime();
if (nanos <= 0L) {
// 假如线程等候时间到了就需求从等候队列当中将当前线程对应的节点移除队列
removeWaiter(q);
return state;
}
LockSupport.parkNanos(this, nanos);
}
else
// 假如不是超时阻塞的话 直接将这个线程挂起即可
// 这个函数前面曾经提到了就是将当前线程唤醒
// 就是将调用 park 办法的线程唤醒
LockSupport.park(this);
}
}
复制代码
finishCompletion办法,这个办法是用于将一切被get函数阻塞的线程唤醒。
private void finishCompletion() {
// assert state > COMPLETING;
for (WaitNode q; (q = waiters) != null;) {
// 假如能够将 waiter 设置为 null 则进入 for 循环 在 for 循环内部将一切线程唤醒
// 这个操作也是原子操作
if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
for (;;) {
Thread t = q.thread;
// 假如线程不等于空 则需求将这个线程唤醒
if (t != null) {
q.thread = null;
// 这个函数曾经提到了 就是将线程 t 唤醒
LockSupport.unpark(t);
}
// 得到下一个节点
WaitNode next = q.next;
// 假如节点为空 阐明一切的线程都曾经被唤醒了 能够返回了
if (next == null)
break;
q.next = null; // unlink to help gc
q = next; // 唤醒下一个节点
}
break;
}
}
done();// 这个函数是空函数没有完成
callable = null; // to reduce footprint
}
复制代码
cancel办法,这个办法主要是取消FutureTask的执行过程。
public boolean cancel(boolean mayInterruptIfRunning) {
// 参数 mayInterruptIfRunning 表示可能在线程执行的时分中缀
// 只要 state == NEW 并且可以将 state 的状态从 NEW 变成 中缀或者取消才干够执行下面的 try 代码块
// 否则直接返回 false
if (!(state == NEW &&
UNSAFE.compareAndSwapInt(this, stateOffset, NEW,
mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
return false;
try { // in case call to interrupt throws exception
// 假如在线程执行的时分中缀代码就执行下面的逻辑
if (mayInterruptIfRunning) {
try {
Thread t = runner; // 得到正在执行 call 函数的线程
if (t != null)
t.interrupt();
} finally { // final state
// 将 state 设置为 INTERRUPTED 状态
UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
}
}
} finally {
// 唤醒被 get 函数阻塞的线程
finishCompletion();
}
return true;
}
复制代码
上面谈到了FutureTask当中最中心的一些函数,这些过程还是十分复杂的,必需理好思绪认真剖析才干够真正了解。除了上面的这些函数之外,在FutureTask当中还有一些其他的函数没有谈到,由于这些函数不会影响我们的了解,假如大家感兴味能够自行去看FutureTask源代码!