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

本文详细解析了如何使用FutureTask获取线程执行的返回值,通过源码分析,解释了MyCallable对象如何与FutureTask配合,实现线程执行结果的获取。
版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.youkuaiyun.com/yhl_jxy/article/details/82664829

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


 
  1. package com.lanhuigu.demo.createthread;
  2. import java.util.concurrent.Callable;
  3. import java.util.concurrent.ExecutionException;
  4. import java.util.concurrent.FutureTask;
  5. /**
  6. * 实现Callable接口,获取线程执行返回值
  7. * @author yihonglei
  8. * @date 2018/9/12 16:43
  9. */
  10. public class MyCallable implements Callable<String> {
  11. /**
  12. * 实现Callable中的call方法
  13. * @author yihonglei
  14. * @date 2018/9/12 17:01
  15. */
  16. public String call() throws Exception {
  17. return "Test Callable";
  18. }
  19. public static void main(String[] args) {
  20. /** 根据MyCallable创建FutureTask对象 */
  21. FutureTask<String> futureTask = new FutureTask<>( new MyCallable());
  22. try {
  23. /** 启动线程 */
  24. new Thread(futureTask).start();
  25. /** 获取线程执行返回值 */
  26. String s = futureTask.get();
  27. /** 打印返回值 */
  28. System.out.println(s);
  29. } catch (InterruptedException e) {
  30. e.printStackTrace();
  31. } catch (ExecutionException e) {
  32. e.printStackTrace();
  33. }
  34. }
  35. }

程序运行结果:

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

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

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


 
  1. package java.util.concurrent;
  2. @FunctionalInterface
  3. public interface Callable<V> {
  4. V call() throws Exception;
  5. }

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

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


 
  1. package java.util.concurrent;
  2. import java.util.concurrent.locks.LockSupport;
  3. public class FutureTask<V> implements RunnableFuture<V> {
  4. ......
  5. }

 
  1. package java.util.concurrent;
  2. public interface RunnableFuture<V> extends Runnable, Future<V> {
  3. /**
  4. * Sets this Future to the result of its computation
  5. * unless it has been cancelled.
  6. */
  7. void run();
  8. }

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


 
  1. /** 根据MyCallable创建FutureTask对象 */
  2. FutureTask< String> futureTask = new FutureTask<>( new MyCallable());

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


 
  1. /** 启动线程 */
  2. new Thread( futureTask) .start();

start()方法源码如下:


 
  1. public synchronized void start() {
  2. if (threadStatus != 0)
  3. throw new IllegalThreadStateException();
  4. group.add( this);
  5. boolean started = false;
  6. try {
  7. start0();
  8. started = true;
  9. } finally {
  10. try {
  11. if (!started) {
  12. group.threadStartFailed( this);
  13. }
  14. } catch (Throwable ignore) {
  15. /* do nothing. If start0 threw a Throwable then
  16. it will be passed up the call stack */
  17. }
  18. }
  19. }
  20. // 本地方法
  21. private native void start0();

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

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


 
  1. public void run() {
  2. if (state != NEW ||
  3. !UNSAFE.compareAndSwapObject( this, runnerOffset,
  4. null, Thread.currentThread()))
  5. return;
  6. try {
  7. /**
  8. 这里的callable就是我们创建FutureTask的时候传进来的MyCallable对象,
  9. 该对象实现了Callable接口的call()方法。
  10. */
  11. Callable<V> c = callable;
  12. if (c != null && state == NEW) {
  13. V result;
  14. boolean ran;
  15. try {
  16. /**
  17. 调用Callable的call方法,即调用实现类MyCallable的call()方法,
  18. 执行完会拿到MyCallable的call()方法的返回值“Test Callable”。
  19. */
  20. result = c.call();
  21. ran = true;
  22. } catch (Throwable ex) {
  23. result = null;
  24. ran = false;
  25. setException(ex);
  26. }
  27. if (ran)
  28. /** 将返回值传入到set方法中,这里是能获取线程执行返回值的关键 */
  29. set(result);
  30. }
  31. } finally {
  32. // runner must be non-null until state is settled to
  33. // prevent concurrent calls to run()
  34. runner = null;
  35. // state must be re-read after nulling runner to prevent
  36. // leaked interrupts
  37. int s = state;
  38. if (s >= INTERRUPTING)
  39. handlePossibleCancellationInterrupt(s);
  40. }
  41. }

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

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


 
  1. protected void set(V v) {
  2. if (UNSAFE.compareAndSwapInt( this, stateOffset, NEW, COMPLETING)) {
  3. /**
  4. 将MyCallable执行call()方法的返回值传进来赋值给了outcome,
  5. 这个outcome是FutureTask的一个成员变量。
  6. 该变量用于存储线程执行返回值或异常堆栈,通过对应的get()方法获取值。
  7. private Object outcome;
  8. */
  9. outcome = v;
  10. UNSAFE.putOrderedInt( this, stateOffset, NORMAL); // final state
  11. finishCompletion();
  12. }
  13. }

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

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

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


 
  1. /** 获取线程执行返回值 */
  2. String s = futureTask. get();

FutureTask中get()方法的源码:


 
  1. public V get() throws InterruptedException, ExecutionException {
  2. int s = state;
  3. if (s <= COMPLETING)
  4. s = awaitDone( false, 0L);
  5. /** 调用report方法 */
  6. return report(s);
  7. }
  8. private V report(int s) throws ExecutionException {
  9. /** outcome赋值给Object x */
  10. Object x = outcome;
  11. if (s == NORMAL)
  12. /** 返回outcome的值,也就是线程执行run()方法时通过set()方法放进去的MyCallable的call()执行的返回值 */
  13. return (V)x;
  14. if (s >= CANCELLED)
  15. throw new CancellationException();
  16. throw new ExecutionException((Throwable)x);
  17. }

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的过程,饶了一圈而已。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值