FutureTask继承体系
Runnable和Callable是多线程中的两个任务接口,实现接口的类将拥有多线程的功能,FutureTask类与这两个类是息息相关!
FutureTask的构造方法
构造方法1 接收Callable对象
ini
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
构造方法2 接收Runnable对象和一个泛型的result
ini
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW; // ensure visibility of callable
}
原来,FutureTask内部维护Callable类型的成员变量,对于Callable任务,直接赋值即可。而对于Runnable任务,需要先调用Executors.callable()把Runnable先包装成Callable。
ini
Executors.callable(runnable, result);
这行代码用了适配器模式,你给我一个runnable对象,我还你一个callable对象。
typescript
public static <T> Callable<T> callable(Runnable task, T result) {
if (task == null)
throw new NullPointerException();
return new Executors.RunnableAdapter<T>(task, result);
}
RunnableAdapter是Executors中的静态内部类,上面代码意思是调用该静态内部类的构造方法,生成RunnableAdapter 对象,而RunnableAdapter对象实现了Callable接口,根据多态也就相当于得到了一个Callable对象。
kotlin
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;
}
}
unnableAdapter作为Callable的适配器,也拥有call方法,这就是适配器模式。
如果你是用第二种方式来构造FutureTask对象,因为传入的是Runnable,Runnable的run方法是没有返回值的,而Callable的call方法是有返回值的,所以这边就折中一下,返回值需要你在构建FutureTask对象时自己传进去,最后再原封不动地还给你。
如果你是用第一种方式来构造FutureTask对象,那就简单多了,直接传入一个Callable对象即可,返回值你自己决定。
为什么要用FutureTask?
多线程是Java进阶的难点,也是面试的重灾区,请确保你把上面的代码都理解了之后再来看这一节。
我们再回过头来想想,如何使用多线程呢,是不是有3个方法?如果记不得了请回过去看看上一个章节【线程类】。
第1种方法是直接继承Thread类,重写run方法。
第2种方法是实现Runnable接口,然后还是要靠Thread类的构造器,把Runnable传进去,最终调用的就是Runnable的run方法。
第3种方法是用线程池技术,用ExecutorService去提交Runnable对象/Callable对象,区别是Runnable没有返回值,Callable对象有返回值。
你发现没有,不管你用哪种方式,最终都是要靠Thread类去开启线程的。因为,有且仅有Thread类能通过start0()方法向操作系统申请线程资源(本地方法)
第一种方法因为耦合性太高,很少会使用,实际开发中我们一般都会使用线程池技术,所以第3种方法是有实战意义的。那么问题来了,Runnable和Callable对象都可以被用作线程池的任务,就有人会乱用了啊,有的人喜欢Runnable,有的喜欢Callable,到时候项目的代码就乱成一锅粥啦!
所以,我私以为Java的创始人意识到这一点,就干脆搞一个FutureTask出来一统江湖。我说的这么白,应该都明白了吧,嘿嘿。
FutureTask的7种状态
java
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;
状态含义分别是:
● 0-刚创建
● 1-计算中
● 2-完成
● 3-抛异常
● 4-任务取消
● 5-任务即将被打断
● 6-任务被打断
为什么要设置这些状态呢,那是因为FutureTask=任务+结果,调用者何时可以去获取这个结果result呢?FutureTask在调用get方法时,会去判断当前任务的状态,只有当任务完成才会给你实际的result,因此get方法是阻塞的。
FutureTask的get() 方法
都是调用awaitDone方法
ini
private int awaitDone(Boolean timed, long nanos)
throws InterruptedException {
//如果设置了超时时间timed=true,那么deadline就是超时时间,超过就超时了
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;
//如果执行状态已经完成或者发生异常,直接返回结果
if (s > COMPLETING) {
if (q != null)
q.thread = null;
return s;
}
//如果执行状态是正在执行,说明任务已经完成.那么现在需要给其他正在执行的任务让路,挂起线程.
else if (s == COMPLETING) // cannot time out yet
Thread.yield();
//第一次进入循环,创建等待节点
else if (q == null)
q = new WaitNode();
//将节点加入到等待队列中,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);
}
}
触发流程:
1.第一轮for循环,执行逻辑q == null,新建等待节点q,循环结束
2.第二轮for循环,执行!q,入队,循环结束.
3.第三轮for循环,进行阻塞等待或者阻塞特定时间,直到阻塞被其他线程唤醒.
4.唤醒后第四轮for循环,根据前三个条件进入对应的逻辑中
finishCompletion
该方法主要用于唤醒线程.当任务结束或者异常时,会调用该方法
被唤醒的线程就会从awaitDown方法中的LockSupport的park或者parkNanos方法处唤醒,然后继续执行awaitDown方法
ini
private void finishCompletion() {
// 遍历等待节点
for (WaitNode q; (q = waiters) != null;) {
if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
for (;;) {
Thread t = q.thread;
if (t != null) {
q.thread = null;
// 唤醒等待线程
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
}
FutureTask的run方法
最终是存储到outcome对象了,简而言之,FutureTask的run方法的作用就是运行callable的call方法,拿到返回值保存到outcome对象,等待有人来取。