FutureTask源码解析

2023年 9月 21日 60.9k 0

FutureTask继承体系

Runnable和Callable是多线程中的两个任务接口,实现接口的类将拥有多线程的功能,FutureTask类与这两个类是息息相关!

image.png

FutureTask的构造方法

构造方法1 接收Callable对象

public FutureTask(Callable callable) {
    if (callable == null)
        throw new NullPointerException();
    this.callable = callable;
    this.state = NEW;       // ensure visibility of callable
}

构造方法2 接收Runnable对象和一个泛型的result

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。

Executors.callable(runnable, result);

这行代码用了适配器模式,你给我一个runnable对象,我还你一个callable对象。

public static  Callable callable(Runnable task, T result) {
    if (task == null)
        throw new NullPointerException();
    return new Executors.RunnableAdapter(task, result);
}

RunnableAdapter是Executors中的静态内部类,上面代码意思是调用该静态内部类的构造方法,生成RunnableAdapter对象,而RunnableAdapter对象实现了Callable接口,根据多态也就相当于得到了一个Callable对象。

static final class RunnableAdapter implements Callable {
    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种状态

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() 方法

image.png

都是调用awaitDone方法

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

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论