

腾讯一面:Thread、Runnable、Callable、Future、FutureTask,谈谈他们的关系?
source link: https://developer.51cto.com/article/714752.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

大家好,我是楼仔!
Thread、Runnable、Callable、Future、FutureTask,你能详细讲出他们的内部关系么?这也是面试经常问到的问题。
这篇文章主要告诉大家各种对象内部的关系,能达到灵活运用的境界,下面是文章目录:

1. Thread 和 Runnable
1.1 Thread
我们先看一下 Thread 最简单的使用姿势:
public class MyThread extends Thread {
public MyThread(String name) {
super(name);
}
@Override
public void run() {
String name = Thread.currentThread().getName();
System.out.println(name + "已经运行");
}
public static void main(String[] args) {
new MyThread("线程一").start();
}
}
线程包含 4 个状态:创建 -> 就绪 -> 运行 -> 结束。
当执行 start() 后,线程进入就绪状态,当对应的线程抢占到 cpu 调度资源之后,进入运行状态,此时调用的是 run 方法,执行完毕之后就结束了。
1.2 Runnable
我们看一下 Runnable 最简单的使用姿势:
public class MyTask implements Runnable {
@Override
public void run() {
String name = Thread.currentThread().getName();
System.out.println(name + "已经运行");
}
public static void main(String[] args) {
new Thread(new MyTask(),"线程二").start();
}
}
这里 MyTask 就是一个 Runnable,实现了 run() 方法,作为 Thread() 的入参。
基本所有同学都知道这样使用,但是你们知道原理么?
1.3 Thread 和 Runnable 的关系
我们看一下 Runnable 的接口定义:
public interface Runnable {
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see java.lang.Thread#run()
*/
public abstract void run();
}
英文翻译大致如下:当一个对象继承并实现了 run() 方法,当线程 start() 后,会在该线程中单独执行该对象的 run() 方法。
这段翻译,基本就告诉了 Runnable 和 Thread 的关系:
- MyTask 继承 Runnable,并实现了 run() 方法;
- Thread 初始化,将 MyTask 作为自己的成员变量;
- Thread 执行 run() 方法,线程处于“就绪”状态;
- 等待 CPU 调度,执行 Thread 的 run() 方法,但是 run() 的内部实现,其实是执行的 MyTask.run() 方法,线程处于“运行”状态。
这里面的第2、4步,需要对照着源码看看。
在 Thread 初始化时,MyTask 作为入参 target,最后赋值给 Thread.target:


当执行 Thread.run() 时,其实是执行的 target.run(),即 MyTask.run(),这个是典型的策略模式:

2. Callable 、Future 和 FutureTask
先看一下它们的整体关系图谱:

我刚开始看到这幅图,感觉 Java 真是麻烦,已经有了 Thread 和 Runnable 这两种创建线程的方式,为啥又搞这 3 个东西呢?
其实对于 Thread 和 Runable,其 run() 都是无返回值的,并且无法抛出异常,所以当你需要返回多线程的数据,就需要借助 Callable 和 Feature。
2.1 Callable
Callable 是一个接口,里面有个 V call() 方法,这个 V 就是我们的返回值类型:
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
我们一般会用匿名类的方式使用 Callable,call() 中是具体的业务逻辑:
Callable<String> callable = new Callable<String>() {
@Override
public String call() throws Exception {
// 执行业务逻辑 ...
return "this is Callable is running";
}
};
这里抛出一个问题,这个 callable.call() 和 Thread.run() 是什么关系呢?
2.2 FutureTask
通过关系图谱,FutureTask 继承了 RunnableFuture,RunnableFuture 继承了 Runnable 和 Future:
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 !!!
这里就有点意思了,既然 FutureTask 是个 Runnable,肯定就需要实现 FutureTask.run() 方法,那么 FutureTask 也可以作为 Thread 的初始化入参,使用姿势如下:
new Thread(FutureTask对象).start();
所以当执行 Thread.run() 时,其实是执行的 FutureTask.run(),这个是我们破解的第一层。
下面我们再破解 FutureTask.run() 和 Callable.call() 的关系。
2.3 Callable 和 FutureTask 的关系
FutureTask 初始化时,Callable 必须作为 FutureTask 的初始化入参:

当执行 FutureTask.run() 时,其实执行的是 Callable.call():

所以,这里又是一个典型的策略模式 !!!
现在我们应该可以很清楚知道 Thread 、Runnable、FutureTask 和 Callable 的关系:
Thread.run() 执行的是 Runnable.run();
FutureTask 继承了 Runnable,并实现了 FutureTask.run();
FutureTask.run() 执行的是 Callable.run();
依次传递,最后 Thread.run(),其实是执行的 Callable.run()。
所以整个设计方法,其实就是 2 个策略模式,Thread 和 Runnable 是一个策略模式,FutureTask 和 Callable 又是一个策略模式,最后通过 Runnable 和 FutureTask 的继承关系,将这 2 个策略模式组合在一起。
嗯嗯。。。我们是不是把 Future 给忘了~~
2.4 Future
为什么要有 Future 呢?我再问一个问题,大家可能就知道了。
我们通过 FutureTask,借助 Thread 执行线程后,结果数据我们怎么获取到呢?这里就需要借助到 Future。
我们看一下 Future 接口:
public interface Future<V> {
// 取消任务,如果任务正在运行的,mayInterruptIfRunning为true时,表明这个任务会被打断的,并返回true;
// 为false时,会等待这个任务执行完,返回true;若任务还没执行,取消任务后返回true,如任务执行完,返回false
boolean cancel(boolean mayInterruptIfRunning);
// 判断任务是否被取消了,正常执行完不算被取消
boolean isCancelled();
// 判断任务是否已经执行完成,任务取消或发生异常也算是完成,返回true
boolean isDone();
// 获取任务返回结果,如果任务没有执行完成则等待完成将结果返回,如果获取的过程中发生异常就抛出异常,
// 比如中断就会抛出InterruptedException异常等异常
V get() throws InterruptedException, ExecutionException;
// 在规定的时间如果没有返回结果就会抛出TimeoutException异常
V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
}
对于 FutureTask,Callable 就是他的任务,而 FutureTask 内部维护了一个任务状态,所有的状态都是围绕这个任务来进行的,随着任务的进行,状态也在不断的更新。
FutureTask 继承了 Future,实现对任务的取消、数据获取、任务状态判断等功能。
比如我们经常会调用 get() 方法获取数据,如果任务没有执行完成,会将当前线程放入阻塞队列等待,当任务执行完后,会唤醒阻塞队列中的线程。
3. 具体实例
private static List<String> processByMultiThread(Integer batchSize) throws ExecutionException, InterruptedException {
List<String> output = new ArrayList<>();
// 获取分批数据
List<List<Integer>> batchProcessData = getProcessData(batchSize);
// 启动线程
List<FutureTask<List<String>>> futureTaskList = new ArrayList<>();
for (List<Integer> processData : batchProcessData) {
Callable<List<String>> callable = () -> processOneThread(processData);
FutureTask<List<String>> futureTask = new FutureTask<>(callable);
new Thread(futureTask).start(); // 启动线程
futureTaskList.add(futureTask);
}
// 获取线程返回的数据
for (FutureTask futureTask : futureTaskList) {
List<String> processData = (List<String>) futureTask.get();
output.addAll(processData);
}
return output;
}
这个示例很简单:
- 先将数据按照 batchSize 分成 N 批;
- 启动 N 个线程,去执行任务;
- 通过 futureTask.get() 获取每个线程数据,并汇总输出。
这个示例其实不太适合线上的场景,因为每次调用都会初始化线程,如果调用过多,内存可能会被撑爆,需要借助线程池。
Recommend
-
67
FutureTask类 重点是那个股票交易处理程序的例子,认真看三遍。本文花了三个小时。 GitHub代码欢迎star。 小白认为学习语言最好的方式就是模仿、思考别人为什么这么写。 FutureTask类同时实现类Runnable接口和Future接口。因此,
-
78
所谓异步任务,就是不在当前线程中进行执行,而是另外起一个线程让其执行。那么当前线程如果想拿到其执行结果,该怎么办呢? 如果我们使用一个公共变量作为结果容器,两个线程共用这个值,那么应该是可以拿到结果的,但是这样一来...
-
11
#Callable、Future和FutureTask 在前面的文章中我们讲述了创建线程的2种方式,一种是直接继承Thread,另外一种就是实现Runnable接口。 这2种方式都有一个缺陷就是:在执行完任务之后无法获取执行结果。 如果需要获取执行结果,就必须通过共享变量或者使...
-
9
在 Java 中,Runnable 接口表示一个没有返回结果的任务,而 Callable 接口表示具有返回结果的任务。在并发编程中,异步执行任务,再获取任务结果,可以提高系统的吞吐量。Future 接口应运而生,它表示异步任务的执行结果,并提供了检查任务是否执行完、取...
-
7
大家好,我是冰河~~ 在Java的多线程编程中,除了Thread类和Runnable接口外,不得不说的就是Callable接口Future接口了。使用继承Thread类或者实现Runnable接口的线程,无法返回最终的执行结果数据,只能等待线程执行完成。此时,如果...
-
6
这是JUC包中,与Future异步等相关的类图. 简单的介绍以及记录. 偏意识流. 首先看一下最基本的Runnable Runnabl...
-
5
【JUC】可回调任务FutureTask原理发布于 2 月 4 日上一篇观察ThreadPoolExecutor的submit方法的时候,...
-
3
摘要: J.U.C是Java并发编程中非常重要的工具包,今天,我们就来着重讲讲J.U.C里面的FutureTask、Fork/Join框架和BlockingQueue。本文分享自华为云社区《
-
9
使用Thread类和Runnable接口实现多线程的区别 先看两种实现方式的步骤: public class ThreadDemo{ public static void main(String[] args) { for (int i = 0; i < 5; i++...
-
5
温故知新----线程之Runnable与Callable接口的本质区别 预备知识:Java中的线程对象是Thread,新建线程也只有通过创建Thread对象的实例来创建。 1 Runnable没有...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK