多线程 | FutureTask 执行流程

简介: 多线程 | FutureTask 执行流程

       在 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 类的定义,该类的定义如下:

publicclassFutureTask<V>implementsRunnableFuture<V> {

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

publicinterfaceRunnableFuture<V>extendsRunnable, Future<V> {
voidrun();
}

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

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

publicinterfaceFuture<V> {
booleancancel(booleanmayInterruptIfRunning);
booleanisCancelled();
booleanisDone();
Vget() throwsInterruptedException, ExecutionException;
Vget(longtimeout, TimeUnitunit)
throwsInterruptedException, 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 方法的代码如下:

publicvoidrun() {
if (state!=NEW||!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return;
try {
Callable<V>c=callable;
if (c!=null&&state==NEW) {
Vresult;
booleanran;
try {
result=c.call();
ran=true;
            } catch (Throwableex) {
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 interruptsints=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 的属性,其定义如下:

privateCallable<V>callable;

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

publicFutureTask(Callable<V>callable) {
if (callable==null)
thrownewNullPointerException();
this.callable=callable;
this.state=NEW;       // ensure visibility of callable}

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

result=c.call()

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

@FunctionalInterfacepublicinterfaceCallable<V> {
Vcall() throwsException;
}

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

set(result)

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

protectedvoidset(Vv) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome=v;
UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final statefinishCompletion();
    }
}

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

privateObjectoutcome;

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

@SuppressWarnings("unchecked")
privateVreport(ints) throwsExecutionException {
Objectx=outcome;
if (s==NORMAL)
return (V)x;
if (s>=CANCELLED)
thrownewCancellationException();
thrownewExecutionException((Throwable)x);
}
publicVget() throwsInterruptedException, ExecutionException {
ints=state;
if (s<=COMPLETING)
s=awaitDone(false, 0L);
returnreport(s);
}

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


FutureTask 实例

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

publicclassFutureTaskTest{
staticpublicclassCallableThreadimplementsCallable<Integer> {
/*** 此处的call方法是实际的线程方法,写我们的异步任务* 这里是一个简单的计算100的累加和*/@OverridepublicIntegercall() throwsException {
intsum=0;
for (inti=1; i<=100; i++) {
sum+=i;
            }
returnsum;
        }
    }
publicstaticvoidmain(String[] args) {
CallableThreadcallableThread=newCallableThread();
FutureTask<Integer>integerFutureTask=newFutureTask<>(callableThread);
Threadthread=newThread(integerFutureTask);
thread.start();
try {
// 这里通过FutureTask.get()方法来阻塞获取结算的结果System.out.println(integerFutureTask.get());
        } catch (InterruptedException|ExecutionExceptione) {
e.printStackTrace();
        }
    }
}

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


总结

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

相关文章
|
4月前
|
Java
解析Java线程池:参数详解与执行流程
解析Java线程池:参数详解与执行流程
51 1
|
28天前
|
消息中间件 存储 前端开发
面试官:说说停止线程池的执行流程?
面试官:说说停止线程池的执行流程?
38 2
面试官:说说停止线程池的执行流程?
|
2月前
|
Java API 调度
JUC线程池: FutureTask详解
总而言之,FutureTask是Java并发编程中一个非常实用的类,它在异步任务执行及结果处理方面提供了优雅的解决方案。在实现细节方面可以搭配线程池的使用,以及与Callable接口的配合使用,来完成高效的并发任务执行和结果处理。
30 0
|
3月前
|
存储 安全 Java
Java面试题:如何在Java应用中实现有效的内存优化?在多线程环境下,如何确保数据的线程安全?如何设计并实现一个基于ExecutorService的任务处理流程?
Java面试题:如何在Java应用中实现有效的内存优化?在多线程环境下,如何确保数据的线程安全?如何设计并实现一个基于ExecutorService的任务处理流程?
40 0
|
3月前
|
监控 Java 开发者
Java面试题:解释Java内存模型中的内存顺序规则,Java中的线程组(ThreadGroup)的工作原理,Java中的FutureTask的工作原理
Java面试题:解释Java内存模型中的内存顺序规则,Java中的线程组(ThreadGroup)的工作原理,Java中的FutureTask的工作原理
23 0
|
3月前
|
存储 算法 Java
Java面试题:详细描述Java堆内存的垃圾回收过程,解释Java中的线程池(ThreadPool)的工作原理,解释Java中的FutureTask的工作原理
Java面试题:详细描述Java堆内存的垃圾回收过程,解释Java中的线程池(ThreadPool)的工作原理,解释Java中的FutureTask的工作原理
27 0
|
5月前
|
Linux API 调度
xenomai内核解析-xenomai实时线程创建流程
本文介绍了linux硬实时操作系统xenomai pthread_creta()接口的底层实现原理,解释了如何在双内核间创建和调度一个xenomai任务。本文是基于源代码的分析,提供了详细的流程和注释,同时给出了结论部分,方便读者快速了解核心内容。
160 0
xenomai内核解析-xenomai实时线程创建流程
|
5月前
|
存储 NoSQL Redis
深入浅出Redis(二):Redis单线程模型与通信流程
深入浅出Redis(二):Redis单线程模型与通信流程
|
12月前
|
存储 安全 Java
【线程池添加工作线程的流程】
【线程池添加工作线程的流程】
|
5月前
|
Java API
java多线程之FutureTask、Future、CompletableFuture
java多线程之FutureTask、Future、CompletableFuture
218 0