FutureTask源码解析

1. 前言

创建线程的方式中,我们可以直接继承Thread实现Callable接口来创建线程,但是这两种创建线程的方式不能返回执行的结果。于是从JDK1.5开始提供了Callable接口Future接口,这两种创建线程的方式可以在执行完任务之后返回执行结果

Future模式可以让线程异步获得执行的结果,而不用等到线程等到run()方法里面执行完再执行之后的逻辑。

这里解释一下什么是异步:

在这里插入图片描述

A口为主线程,B口为辅助线程,当B口阻塞时并不影响A口通水

我们写个小demo证明一下Future执行线程是异步的。

// Future是一个接口,FutureTask是Future的实现类
package FutureTask;

import java.util.concurrent.*;

public class Demo02 {
    public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
        long start = System.currentTimeMillis();
        FutureTask<Integer> futureTask1 = new FutureTask<Integer>(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                // 睡了2000毫秒
                Thread.sleep(2000);
                return 1;
            }
        });

        FutureTask<Integer> futureTask2 = new FutureTask<Integer>(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                // 睡了1000毫秒
                Thread.sleep(1000);
                return 2;
            }
        });
        // 这个线程要睡2000毫秒
        new Thread(futureTask1).start();
        // 这个线程要睡1000毫秒
        new Thread(futureTask2).start();
        Integer s1 = futureTask1.get();
        Integer s2 = futureTask2.get();
        Integer s3 = s1 + s2;
        long end = System.currentTimeMillis();
        System.out.println("结果:" + s3);
        // 输出总共执行的时间
        System.out.println("花费时间:" + (end - start) + "毫秒");
    }
}

/*
如果Future不是异步的,那最后输出的时间一定大于3000毫秒,那我们接下来来看一下执行的结果。
*/

结果如下:

在这里插入图片描述

我们可以看到,花费的时间小于3000毫秒,这也说明利用Future创建出的辅助线程与主线程是异步执行的。Future是一个接口,如果想要使用Future的功能,必须实现接口。我们接下来要介绍的FutureTask就是Future实现类

2. FutureTask继承体系

在这里插入图片描述

我们看到FutureTask实现了RunnableFuture接口,其中RunnableFuture接口继承了RunnableFuture接口,因此我们可以说FutureTask实现了Future接口和Runnable接口,但是我们需要注意的是,FutureTask也实现了Callable接口,虽然这并没有在继承体系中看出,但是可以从FutureTask的源码中看出。接下来我们来看看FutureTaskRunnableFuture的继承体系部分的源码。

  • FutureTask继承体系
// <V>指的是泛型,里面传入要返回的结果的类型
public class FutureTask<V> implements RunnableFuture<V> 
  • RunnableFuture继承体系
// <V>指的是泛型,里面传入的是要返回的结果的类型
public interface RunnableFuture<V> extends Runnable, Future<V> {
    void run();
}
# FutureTask使用场景
FutureTask用于异步执行或取消任务的场景。通过传入Runnable的实现类或Callable的实现类给FutureTask对象,
之后将FutureTask对象传给Thread对象或线程池。可以通过FutureTask对象.get()方法可以异步返回执行结果,
如果任务还没有执行完,则调用get()方法的线程会陷入阻塞,直到任务执行完。无论有多少个线程调用get()方法,
FutureTask中的run()逻辑只会执行一次。我们还可以调用FutureTask对象的cancel()方法取消掉当前任务。

3. 源码分析

3.1 成员变量

state表示当前任务的状态:

  • NEW:表示新创建状态,任务尚未执行
  • COMPLETING:表示当前任务即将结束,但是还未完全结束,返回值还未写入,处于一种临界状态。
  • NORMAL:表示当前任务处于正常结束状态没有发生异常,中断,取消)。
  • EXCEPTIONAL:表示当前任务因为出现异常中断,处于非正常结束状态
  • CANCELLED:表示当前任务因调用cancel而处于被取消状态
  • INTERRUPTING:表示当前任务处于中断中,但是还没有完全中断的阶段。
  • INTERRUPTED:表示当前任务处于已完全中断的阶段。
	// 表示当前任务的状态
    private volatile int state;
    // 表示当前任务的状态是新创建的,尚未执行
    private static final int NEW          = 0;
    // 表示当前任务即将结束,还未完全结束,值还未写,一种临界状态
    private static final int COMPLETING   = 1;
    // 表示当前任务正常结束
    private static final int NORMAL       = 2;
    // 表示当前任务执行过程中出现了异常,内部封装的callable.call()向上抛出异常了
    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对象的时候,会传入一个Callable实现类或Runnable实现类,这个callable存储的就是
	// 传入的Callable实现类或Runnable实现类(Runnable会被使用修饰者设计模式伪装为)callable
    //submit(callable/runnable):其中runnable使用了装饰者设计模式伪装成了callable
    private Callable<V> callable;

    // 正常情况下,outcome保存的是任务的返回结果
    // 不正常情况下,outcome保存的是任务抛出的异常
    private Object outcome; // non-volatile, protected by state reads/writes

    // 保存的是当前任务执行期间,执行任务的线程的引用
    private volatile Thread runner;

    // 因为会有很多线程去get结果,这里把线程封装成WaitNode,一种数据结构:栈,头插头取
    private volatile WaitNode waiters;

	static final class WaitNode {
        // 线程对象
        volatile Thread thread;
        // 下一个WaitNode结点
        volatile WaitNode next;
        WaitNode() { thread = Thread.currentThread(); }
    }

3.2 构造方法

构造方法有两种,一种是只传入一个Callable对象Callable对象返回的结果就是FutureTask对象返回的结果;另一种是传入一个Runnable对象和一个泛型变量,其中Runnable对象会被伪装Runnable对象、传入的泛型变量就是FutureTask执行任务后返回的结果

// 这个构造方法传入一个callable,调用get返回的结果就是callable返回的结果
public FutureTask(Callable<V> callable) {
    if (callable == null)
        throw new NullPointerException();
    this.callable = callable;
    // 设置状态为新创建
    this.state = NEW;       // ensure visibility of callable
}
// 这个构造方法传入一个runnable,一个result变量,调用get方法返回的结果就是传入的result变量
public FutureTask(Runnable runnable, V result) {
    // 装饰者模式,将runnable转化为callable接口
    this.callable = Executors.callable(runnable, result);
    // 设置状态为新创建
    this.state = NEW;       // ensure visibility of callable
}

3.3. 成员方法

3.3.1 run()方法及与其相关的方法

run()里面执行的是当前任务的具体逻辑。里面涉及了setException方法、set方法、handlePossibleCancellationInterrupt方法、finishCompletion方法,这些方法我们都会讲到。

run()方法

里面会调用Callable对象run()方法任务的具体逻辑和一些关于任务状态返回结果的逻辑。

public void run() {
        // 当前任务状态不为new或者runner旧值不为null,说明已经启动过了,直接返回,这里也说明了run()里面的具体逻辑只会
    	// 被执行一次。
        if (state != NEW ||
            !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                         null, Thread.currentThread()))
            return;
        // 只有当任务状态为new并且runner旧值为null才会执行到这里
        try {
            // 传入的callable任务
            Callable<V> c = callable;
            // 当任务不为null并且当前任务状态为新建时才会往下执行
            // 条件1:防止空指针异常
            // 条件2:防止外部线程cacle掉当前任务
            if (c != null && state == NEW) {
                // 储存任务的返回结果
                V result;
                // 储存执行是否成功
                boolean ran;
                try {
                    // 调用callable.run()并返回结果
                    result = c.call();
                    // 正常执行设置ran为true
                    ran = true;
                } catch (Throwable ex) {
                    // callable的run()方法抛出了异常
                    // 设置结果为null
                    result = null;
                    // 执行失败设置ran为false
                    ran = false;
                    // 内部设置outcome为抛出的异常,
                    //并且更新任务状态为EXCEPTIONAL(执行过程中出现了异常)并且唤醒阻塞的线程
                    setException(ex);
                }
                // 如果执行成功(正常执行)
                if (ran)
                    // 内部设置outcome为callable执行的结果,并且更新任务的状态为NORMAL(任务正常执行)并且唤醒阻塞的线程
                    set(result);
            }
        } finally {
            // runner must be non-null until state is settled to
            // prevent concurrent calls to run()
            // 将当前任务的线程设置为null
            runner = null;
            // state must be re-read after nulling runner to prevent
            // leaked interrupts
            // 当前任务的状态
            int s = state;
            // 如果state>=INTERRUPTING,说明当前任务处于中断中或已中断状态
            if (s >= INTERRUPTING)
                // 如果当前任务处于中,则执行这个方法线程会不断让出cpu直到任务处于已中断状态
                handlePossibleCancellationInterrupt(s);
        }
    }

setException(Throwable t)方法

如果在执行run()方法的过程中出现了异常会执行这个方法,里面具体的逻辑是:

  • 设置任务的状态为EXCEPTIONAL(表示因为出现异常而非正常完成)
  • 设置outcome(返回结果)Callable对象的run()方法抛出的异常
  • 执行finishCompletion()方法唤醒因为调用get()方法而陷入阻塞的线程
protected void setException(Throwable t) {
        // 如果当前任务的状态是新建状态,则设置任务状态为临界状态(即将完成)
        if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
            // 设置outcome(结果)为callable.run()抛出的异常
            outcome = t;
            // 设置当前任务的状态为出现中断异常
            UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
            // 唤醒调用get()的所有等待的线程并清空栈
            finishCompletion();
        }
    }

set(V v)方法

如果执行run()方法正常结束(没有出现异常)会执行这个方法,里面的具体逻辑是:

  • 设置任务的状态为NORMAL(表示正常结束)
  • 设置outcome(返回结果)为Callable对象调用run()方法返回的结果
  • 唤醒因为调用get()方法陷入阻塞的线程
protected void set(V v) {
        // 如果当前任务的状态为新建状态,则设置当前任务的状态为临界状态(即将完成)
        if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
            // 设置outcome(结果)为callable.run()返回的结果
            outcome = v;
            // 设置当前任务的状态为正常结束
            UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
            // 唤醒调用get()的所有等待的线程并清空栈
            finishCompletion();
        }
    }

handlePossibleCancellationInterrupt(int s)方法

这个方法在run()方法里可能会执行,当任务的状态为中断中时,抢到cpu的线程会释放cpu资源,直到任务状态更新为已中断状态。

private void handlePossibleCancellationInterrupt(int s) {

        if (s == INTERRUPTING)
            while (state == INTERRUPTING)
                Thread.yield(); // wait out pending interrupt

    }

finishCompletion()方法

任务执行完成(正常结束和非正常结束都代表任务执行完成)会调用这个方法来唤醒所有因调用get()方法陷入阻塞的线程

private void finishCompletion() {
        // assert state > COMPLETING;
        // 如果条件成立,说明当前有陷入阻塞的线程
        for (WaitNode q; (q = waiters) != null;) {
            if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
                for (;;) {
                    // 获取当前节点封装的thread
                    Thread t = q.thread;
                    // 防止空指针异常
                    if (t != null) {
                        // 设置q.thread为null,方便GC
                        q.thread = null;
                        // 唤醒当前节点封装的线程
                        LockSupport.unpark(t);
                    }
                    // 获取下一个WaitNode节点
                    WaitNode next = q.next;
                    // 如果next为空,说明栈现在已经为空,调用get()陷入阻塞的线程已经全部唤醒,直接break
                    if (next == null)
                        break;
                    // 执行到这里说明还有因调用get()而陷入阻塞的线程,自旋接着唤醒
                    // 这里q.next设置为null帮助GC(垃圾回收)
                    q.next = null; // unlink to help gc
                    // 下一个WaitNode节点
                    q = next;
                }
                // 中断
                break;
            }
        }
        // 空方法,子类可以重写
        done();
        // 将callable设置为null,方便GC
        callable = null;        // to reduce footprint
    }

3.3.2 get()方法及与其相关的方法

get()方法

get()方法获取的是任务执行完后返回的结果。对于空参的get()方法来说,如果任务还没有执行完就有线程调用get()方法获取结果,则该线程会陷入阻塞,阻塞的具体方法是awaitDone方法,我们下面会学习。

public V get() throws InterruptedException, ExecutionException {
        int s = state;
        // 判断当前任务的状态是否小于COMPLETING,如果成立说明当前任务的状态要么为新建状态要么为临界状态
        if (s <= COMPLETING)
            // 条件成立会调用awaitDone方法自旋等待直到任务完成
            s = awaitDone(false, 0L);
        return report(s);
    }

对于指定时间含参的get方法来说,如果在指定时间没有返回结果,则会抛出时间超时异常(TimeoutException)

public V get(long timeout, TimeUnit unit)
    throws InterruptedException, ExecutionException, TimeoutException {
    if (unit == null)
        throw new NullPointerException();
    int s = state;
    if (s <= COMPLETING &&
        (s = awaitDone(true, unit.toNanos(timeout))) <= COMPLETING)
        throw new TimeoutException();
    return report(s);
}

awaitDone(boolean timed, long nanos)方法

这个方法是用来阻塞所有因调用get()方法获取结果但是FutureTask任务还没有执行完的线程awaitDone方法在**get()**方法里面会被调用。

上面的话有点绕,我们再理一下,awaitDone用来阻塞线程时需要满足的条件:

  1. 任务还没有执行完
  2. 线程调用了**get()**方法
// 这个方法的作用是等待任务被完成(正常完成或出现异常完成都算完成),被中断,或是被超时
private int awaitDone(boolean timed, long nanos)
        throws InterruptedException {
        final long deadline = timed ? System.nanoTime() + nanos : 0L;
        // 这个WaitNode其实存储的是当前线程
        WaitNode q = null;
        // 表示当前线程代表的WaitNode对象是否入栈成功
        boolean queued = false;
        for (;;) {
            // 如果当前线程出现中断异常,则将该线程代表的WaitNode结点移出栈并抛出中断异常
            if (Thread.interrupted()) {
                removeWaiter(q);
                throw new InterruptedException();
            }
            // 获取当前任务的状态
            int s = state;
            // 如果当前任务状态大于COMPLETING,说明当前任务已经有结果了(任务完成、中断、取消),直接返回任务状态
            if (s > COMPLETING) {
                if (q != null)
                    // 设置q.thread为null,方便GC
                    q.thread = null;
                return s;
            }
            // 当前任务处于临界状态,即将完成,则当前线程释放cpu
            else if (s == COMPLETING) // cannot time out yet
                Thread.yield();
            // 第一次自旋,如果当前WitNode为null,new一个WaitNode结点
            else if (q == null)
                q = new WaitNode();
            // 第二次自旋,如果当前WaitNode节点没有入队,则尝试入队
            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
                // 挂起当前线程,该线程会休眠(什么时候该线程会继续执行呢?除非有其他线程调用unpark()或者中断该线程)
                LockSupport.park(this);
        }
    }

removeWaiter方法

出现中断时,清空栈中的结点

private void removeWaiter(WaitNode node) {
        if (node != null) {
            // 帮助GC
            node.thread = null;
            retry:
            for (;;) {          // restart on removeWaiter race
                for (WaitNode pred = null, q = waiters, s; q != null; q = s) {
                    s = q.next;
                    // 后继节点不为空
                    if (q.thread != null)
                        pred = q;
                    // 前驱结点不为空
                    else if (pred != null) {
                        // 前驱结点的后继结点指向当前结点的后继结点,就相当于将当前节点删去
                        pred.next = s;
                        if (pred.thread == null) // check for race
                            continue retry;
                    }
                    
                    else if (!UNSAFE.compareAndSwapObject(this, waitersOffset,
                                                          q, s))
                        continue retry;
                }
                break;
            }
        }
    }

report(int s)方法

这个方法是真正用来获取任务的返回结果的,这个方法在**get()**方法里面会被调用,如果该方法被调用,说明任务已经执行完了。

private V report(int s) throws ExecutionException {
        // 获取outcome的值
        Object x = outcome;
        // 如果当前任务的状态为正常结束,则返回outcome的值
        if (s == NORMAL)
            return (V)x;
        // 如果当前任务的状态 >= CANCELLED 说明当前任务状态为被取消、或是在中断中、或是已经中断完成
        if (s >= CANCELLED)
            throw new CancellationException();
        throw new ExecutionException((Throwable)x);
    }

3.3.3 cancel(boolean mayInterruptIfRunning)方法

这个方法可以取消当前任务

public boolean cancel(boolean mayInterruptIfRunning) {
        // 条件1成立说明当前任务正在运行中或者处于线程池队列中
        // 条件2成立说明CAS成功可以执行下面的逻辑了,否则返回false,代表cancel失败
        if (!(state == NEW &&
              UNSAFE.compareAndSwapInt(this, stateOffset, NEW,
                  mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
            return false;
        try {    // in case call to interrupt throws exception
            if (mayInterruptIfRunning) {
                try {
                    // 获取当前正在执行的线程,也有可能是null,是null的情况代表当前任务正在队列中,线程还没有获取到它呢
                    Thread t = runner;
                    // 给Thread一个中断信号,如果你的程序是响应中断的,则走中断逻辑;如果你的程序不是响应中断的,则什么也不做
                    if (t != null)
                        t.interrupt();
                } finally { // final state
                    // 设置任务状态为已中断
                    UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
                }
            }
        } finally {
            // 唤醒所有因调用get()而陷入阻塞的线程
            finishCompletion();
        }
        return true;
    }

4. 总结

  • FutureTask采用的是异步的执行方式。
  • FutureTask对象可以使用get()方法返回执行的结果,如果任务还没有执行完,则调用get()线程会陷入阻塞,直到任务执行完。
  • FutureTask对象可以调用cancel方法取消任务的执行
  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
FutureTask是一个实现了RunnableFuture接口的类,它继承了Runnable和Future接口。因此,FutureTask既可以被当作一个Runnable来使用,也可以被当作一个Future来使用。 FutureTask实现了Future接口,完成了对Future接口的基本实现。除了实现了Future接口以外,FutureTask还实现了Runnable接口,这意味着它可以交由Executor执行,也可以直接用线程调用执行(futureTask.run())。 FutureTask具有三种执行状态:未启动、已启动和已完成。未启动指的是在调用run()方法之前,FutureTask处于未启动状态。已启动指的是FutureTask对象的run方法正在执行过程中,FutureTask处于已启动状态。已完成指的是FutureTask正常执行结束,或者被取消,或者执行过程中抛出异常而导致中断而结束。 在ThreadPoolExecutor的submit方法中,返回的是一个Future的实现,而这个实现就是FutureTask的一个具体实例。FutureTask帮助实现了具体的任务执行,并与Future接口中的get方法关联起来。 总结起来,FutureTask是一个能够同时担任Runnable和Future角色的类,它可以作为一个任务提交给线程池执行,也可以通过get方法来获取任务执行的结果。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [【第十二篇】Java 线程池FutureFutureTask【重点】](https://blog.csdn.net/weixin_42039228/article/details/123198358)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [FutureTask简介](https://blog.csdn.net/u014516601/article/details/125123415)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值