FutureTask获取线程返回值原理、源码分析

minger0 2019-07-21

先看一段FutureTask获取线程返回值简单应用的代码,以下基于jdk8进行源码分析。

package com.lanhuigu.demo.createthread;

import java.util.concurrent.Callable;

import java.util.concurrent.ExecutionException;

import java.util.concurrent.FutureTask;

/**

 * 实现Callable接口,获取线程执行返回值

 * @author yihonglei

 * @date 2018/9/12 16:43

 */

public class MyCallable implements Callable<String> {

    /**

     * 实现Callable中的call方法

     * @author yihonglei

     * @date 2018/9/12 17:01

     */

    public String call() throws Exception {

        return "Test Callable";

    }

    public static void main(String[] args) {

        /** 根据MyCallable创建FutureTask对象 */

        FutureTask<String> futureTask = new FutureTask<>(new MyCallable());

        try {

            /** 启动线程 */

            new Thread(futureTask).start();

            /** 获取线程执行返回值 */

            String s = futureTask.get();

            /** 打印返回值 */

            System.out.println(s);

        } catch (InterruptedException e) {

            e.printStackTrace();

        } catch (ExecutionException e) {

            e.printStackTrace();

        }

    }

}

程序运行结果:

FutureTask获取线程返回值原理、源码分析

成功拿到了线程执行的返回值。

以下从源码角度分析拿到返回值的全过程,首先需要简单了解下Callable和FutureTask的结构。

Callable是一个函数式接口,源码如下:

package java.util.concurrent;

@FunctionalInterface

public interface Callable<V> {

    V call() throws Exception;

}

该接口有一个call方法,返回任意类型值。

FutureTask实现RunnableFuture接口,而RunnableFuture继承了Runnable, Future<V>,源码如下:

package java.util.concurrent;

import java.util.concurrent.locks.LockSupport;

public class FutureTask<V> implements RunnableFuture<V> {

 ......

}

package java.util.concurrent;

public interface RunnableFuture<V> extends Runnable, Future<V> {

    /**

     * Sets this Future to the result of its computation

     * unless it has been cancelled.

     */

    void run();

}

所以FutureTask具有Runnable和Future功能,因此,在上面的Demo中,以下代码具有Runable特性:

/** 根据MyCallable创建FutureTask对象 */

FutureTask<String> futureTask = new FutureTask<>(new MyCallable());

创建线程对象,通过start()方法启动线程:

/** 启动线程 */

new Thread(futureTask).start();

start()方法源码如下:

public synchronized void start() {

    if (threadStatus != 0)

        throw new IllegalThreadStateException();

    group.add(this);

    boolean started = false;

    try {

        start0();

        started = true;

    } finally {

        try {

            if (!started) {

                group.threadStartFailed(this);

            }

        } catch (Throwable ignore) {

            /* do nothing. If start0 threw a Throwable then

            it will be passed up the call stack */

        }

    }

}

// 本地方法

private native void start0();

start()方法最后会调用本地方法,由JVM通知操作系统,创建线程,最后线程通过JVM访问到Runnable中的run()方法。

而FutureTask实现了Runnable的run()方法,看下FutureTask中的run()方法源码:

public void run() {

    if (state != NEW ||

        !UNSAFE.compareAndSwapObject(this, runnerOffset,

                                     null, Thread.currentThread()))

        return;

    try {

        /** 

          这里的callable就是我们创建FutureTask的时候传进来的MyCallable对象,

          该对象实现了Callable接口的call()方法。

        */

        Callable<V> c = callable;

        if (c != null && state == NEW) {

            V result;

            boolean ran;

            try {

                /** 

                  调用Callable的call方法,即调用实现类MyCallable的call()方法,

                  执行完会拿到MyCallable的call()方法的返回值“Test Callable”。

                */

                result = c.call();

                ran = true;

            } catch (Throwable ex) {

                result = null;

                ran = false;

                setException(ex);

            }

            if (ran)

                /** 将返回值传入到set方法中,这里是能获取线程执行返回值的关键 */

                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);

    }

}

从run()方法源码可以知道,MyCallabel执行call()方法的返回值被传入到了一个set()方法中,能拿到线程返回值最关键的

就是这个FutureTask的set()方法源码:

protected void set(V v) {

    if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {

        /** 

          将MyCallable执行call()方法的返回值传进来赋值给了outcome,

          这个outcome是FutureTask的一个成员变量。

          该变量用于存储线程执行返回值或异常堆栈,通过对应的get()方法获取值。

          private Object outcome;

        */

        outcome = v;

        UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state

        finishCompletion();

    }

}

到这里,得出一个结论就是,MyCallable执行的call()方法结果通过FutureTask的set()方法存到了成员变量outcome中,

通过我们熟悉的get方法就可以获取到outcome对应赋的值。

在Demo中获取返回值的代码:

/** 获取线程执行返回值 */

String s = futureTask.get();

FutureTask中get()方法的源码:

public V get() throws InterruptedException, ExecutionException {

    int s = state;

    if (s <= COMPLETING)

        s = awaitDone(false, 0L);

    /** 调用report方法 */

    return report(s);

}

private V report(int s) throws ExecutionException {

    /** outcome赋值给Object x */

    Object x = outcome;

    if (s == NORMAL)

       /** 返回outcome的值,也就是线程执行run()方法时通过set()方法放进去的MyCallable的call()执行的返回值 */

       return (V)x;

    if (s >= CANCELLED)

        throw new CancellationException();

    throw new ExecutionException((Throwable)x);

}

get()方法调用report()方法,report()方法会将outcome赋值并返回,set方法成功拿到返回的outcome,

也就是MyCallable()的call()方法执行结果。

到这里,我们大概理解了FutureTask.get()能拿到线程执行返回值的本质原理,也就基于FutureTask的成员变量

outcome进行的set赋值和get取值的过程。

下面简单总结一下过程:

1)FutureTask通过MyCallable创建;

2)new Thread()创建线程,通过start()方法启动线程;

3)执行FutureTask中的run()方法;

4)run()方法中调用了MyCallable中的call()方法,拿到返回值set到FutureTask的outcome成员变量中;

5)FutureTask通过get方法获取outcome对象值,从而成功拿到线程执行的返回值;

其实,本质上就是一个基于FutureTask成员变量outcome进行的set和get的过程,饶了一圈而已。

--------------------- 

原文:https://blog.csdn.net/yhl_jxy/article/details/82664829 

相关推荐

victorzhzh / 0评论 2014-08-24