多线程 | FutureTask 执行流程

码农UP2U

共 4885字,需浏览 10分钟

 ·

2022-05-05 22:29

早期文章



        在 Java 中可以用来创建线程的方式很多,比如由 Java 提供的 Thread、Runnable 等。本文章来介绍使用 FutureTask 创建线程,以及其流程。


Thread 和 Runnable 的问题

        众所周知,使用 Thread、Runnable 创建线程是非常方便的,只要实现 线程的 run 方法即可。但是通过 Thread、Runnable 实现 run 方法创建的线程是无法获取返回结果的,原因是线程方法 run 本身是没有返回值的。但是在很多场景中,我们是需要 异步执行的同时获取其线程执行的返回结果的。因此 Java 除了 Thread、Runnable 外,还提供了 FutureTask,它使得我们可以在异步执行的同时获取到线程的返回结果。


        本文就来介绍一下 FutureTask 类的简单使用。


FutureTask 介绍

        FutureTask 类本身不能用来创建线程,创建线程的工作仍然是由 Thread 类来创建的,FutureTask 和 Runnable 类似,是通过 Thread 类的构造方法传递给 Thread 类的。但是注意观察,Thread 类并没有一个构造方法是用于接受 FutureTask 类型的构造方法。


FutureTask 定义与继承关系

        那么,FutureTask 为什么可以传递给 Thread 类呢?这里重点不是看 Thread 类的构造方法,而是应该看一下 FutureTask 类的定义,该类的定义如下:

public class FutureTask implements RunnableFuture {

        可以看到,FutureTask 实现了 RunnableFuture 接口,那么继续看 RunnableFuture 接口的定义,该定义如下:

public interface RunnableFuture<V> extends Runnable, Future<V> {    void run();}

        从 RunnableFuture 接口的定义可以看出,它继承了 Runnable 接口,那么这样,就可以将 FutureTask 类以构造方法参数的形式传递给 Thread 类了。在 RunnableFuture 接口中有一个 run 方法,那么这就要求实现 RunnableFuture 接口的类要去实现了 run() 方法。这样,FutureTask 类既然实现了 RunnableFuture 接口,那么 FutureTask 类中必然有一个 run 方法是供 Thread 类调用的。

        那么 RunnableFuture 继承的 Future 是什么呢?看一下它的定义,定义如下:

public interface Future<V> {    boolean cancel(boolean mayInterruptIfRunning);    boolean isCancelled();    boolean isDone();    V get() throws InterruptedException, ExecutionException;    V get(long timeout, TimeUnit unit)        throws InterruptedException, ExecutionException, TimeoutException;}

        Future 是一个泛型接口,通过 get() 方法可以阻塞等待获取异步计算的结果,也可以在获取异步计算结果的阻塞上设置一个超时时间。还可以通过 cancel() 方法设置让线程取消、使用 isCancelled() 方法判断线程是否被取消、以及通过 isDone() 方法判断线程是否执行完成。

同样的,Future 接口中的所有实现均在 FutureTask 中可以找到。


        总结一下,FutureTask 实现了 Runnable 接口的 run() 方法(该方法在 Thread.start() 后被调用),实现了 Future 的 get()、cancel()、isCancelled() 和 isDone() 方法。但是,get() 方法是从何处获取到线程的结果呢?这次来看一下 FutureTask 的 run() 方法吧。


FutureTask 实现的 run 方法分析

        FutureTask 实现了 RunnableFuture 接口,RunnableFuture 继承了 Runnable 接口,那么 FutureTask 则可以作为 Thread 类的构造方法的参数传递给 Thread 类,FutureTask 类实现的 run 方法的代码如下:

public void run() {    if (state != NEW ||        !UNSAFE.compareAndSwapObject(this, runnerOffset,                                     null, Thread.currentThread()))        return;    try {        Callable c = callable;        if (c != null && state == NEW) {            V result;            boolean ran;            try {                result = c.call();                ran = true;            } catch (Throwable ex) {                result = null;                ran = false;                setException(ex);            }            if (ran)                set(result);        }    } 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 (s >= INTERRUPTING)            handlePossibleCancellationInterrupt(s);    }}

        从上面的代码中可以看出,FutureTask 的 run() 方法是固定的,并不能在该 run 方法中实现我们自己的业务代码,那应该如何完成自己的线程业务代码呢?来观察一下上面的代码。

上面的代码中,主要看 try 块中的代码,在第 7 行,定义了一个 Callable 的变量 c,在第 9 行,定义了一个泛型的 result 变量在第 12 行,有一句 result = c.call(),在第 20 行有一句 set(result)。我们分别来观察一下这基础。

        第 7 行代码如下:

Callable<V> c = callable;

        第 7 行代码中的 callable 是 FutureTask 的属性,其定义如下:

private Callable callable;

        它是通过构造方法赋值得到的,FutureTask 的构造方法如下:

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

        那么,我们在使用 FutureTask 时,会给它传递 Callable 来实例化它。接着看第 12 行代码,代码如下:

result = c.call()

        可以看出, Callable 有一个 call() 方法,并且有返回值,那么来看一下 Callable 的定义,它的定义如下:

@FunctionalInterfacepublic interface Callable<V> {    call() throws Exception;}

        可以看到它是一个泛型接口,该接口中有一个方法 call(),它有返回值,且可以抛出异常。而这个 call() 方法,就是我们用来写自己业务的线程方法。然后这个方法在 FutureTask 的 run() 方法中被调用。那么返回值呢?同样从第 12 行代码可以看出,c.call() 后把返回值给到了 result 中,最后调用了第 20 行的代码,代码如下:

set(result)

        我们来查看 set() 方法做了什么,代码如下:

protected void set(V v) {    if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {        outcome = v;        UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state        finishCompletion();    }}

        set() 方法将返回值赋值给了 outcome,outcome 也是 FutureTask 的属性,其定义如下:

private Object outcome;

        前面介绍过,get() 方法是用来获取返回结果的,我们通过观察 get() 方法来验证一下,代码如下:

@SuppressWarnings("unchecked")private V report(int s) throws ExecutionException {    Object x = outcome;    if (s == NORMAL)        return (V)x;    if (s >= CANCELLED)        throw new CancellationException();    throw new ExecutionException((Throwable)x);}
public V get() throws InterruptedException, ExecutionException { int s = state; if (s <= COMPLETING) s = awaitDone(false, 0L); return report(s);}

        可以看到,get() 方法返回的就是实际的 outcome 这个属性。


FutureTask 实例

        来一个简单的例子,来看看 FutureTask 的使用,代码如下:

public class FutureTaskTest{    static public class CallableThread implements Callable<Integer{        /**         * 此处的call方法是实际的线程方法,写我们的异步任务         * 这里是一个简单的计算100的累加和         */        @Override        public Integer call() throws Exception {            int sum = 0;
for (int i = 1; i <= 100; i ++) { sum += i; }
return sum; } }
    public static void main(String[] args) { CallableThread callableThread = new CallableThread(); FutureTask integerFutureTask = new FutureTask<>(callableThread);
Thread thread = new Thread(integerFutureTask); thread.start(); try {            // 这里通过FutureTask.get()方法来阻塞获取结算的结果 System.out.println(integerFutureTask.get()); } catch (InterruptedException | ExecutionException e) { e.printStackTrace();        } }}

        上面的示例代码非常的简单,在线程任务中完成一个100内的累加和计算,然后在 main 线程中通过 FutureTask 的 get() 方法来阻塞的获取计算结果。


总结

        总结一下,使用 FutureTask 类时,要配合使用 Thread 类和 Callable 接口,刚开始看可能对三个类之间的关系有点乱,但是当实际的了解了 FutureTask 类的 run() 方法以后,其实整个脉络就清楚了。与 FutureTask 类相关的几个重要接口有 RunnableFuture、Runnable、Future,以及 Callable。能清楚的梳理好它们之间的关系,就可以相对清楚的明白 FutureTask 类了。


        希望本文对你有所帮助!喜欢可以点赞、分享!


87a68fc4e45f84c87afe09a33ff1e2d5.webp

公众号内回复 【mongo】 下载 SpringBoot 整合操作 MongoDB 的文档。

公众号内回复 【cisp知识整理】 下载 CISP 读书笔记。

公众号内回复【java开发手册】获取《Java开发手册》黄山版。


浏览 31
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报