大家好,我是楼仔!,Thread、Runnable、Callable、Future、FutureTask,你能详细讲出他们的内部关系么?这也是面试经常问到的问题。,这篇文章主要告诉大家各种对象内部的关系,能达到灵活运用的境界,下面是文章目录:,
,我们先看一下 Thread 最简单的使用姿势:,线程包含 4 个状态:创建 -> 就绪 -> 运行 -> 结束。,当执行 start() 后,线程进入就绪状态,当对应的线程抢占到 cpu 调度资源之后,进入运行状态,此时调用的是 run 方法,执行完毕之后就结束了。,我们看一下 Runnable 最简单的使用姿势:,这里 MyTask 就是一个 Runnable,实现了 run() 方法,作为 Thread() 的入参。,基本所有同学都知道这样使用,但是你们知道原理么?,我们看一下 Runnable 的接口定义:,英文翻译大致如下:当一个对象继承并实现了 run() 方法,当线程 start() 后,会在该线程中单独执行该对象的 run() 方法。,这段翻译,基本就告诉了 Runnable 和 Thread 的关系:,这里面的第2、4步,需要对照着源码看看。,在 Thread 初始化时,MyTask 作为入参 target,最后赋值给 Thread.target:,
,
,当执行 Thread.run() 时,其实是执行的 target.run(),即 MyTask.run(),这个是典型的策略模式:,
,先看一下它们的整体关系图谱:,
,我刚开始看到这幅图,感觉 Java 真是麻烦,已经有了 Thread 和 Runnable 这两种创建线程的方式,为啥又搞这 3 个东西呢?,其实对于 Thread 和 Runable,其 run() 都是无返回值的,并且无法抛出异常,所以当你需要返回多线程的数据,就需要借助 Callable 和 Feature。,Callable 是一个接口,里面有个 V call() 方法,这个 V 就是我们的返回值类型:,我们一般会用匿名类的方式使用 Callable,call() 中是具体的业务逻辑:,这里抛出一个问题,这个 callable.call() 和 Thread.run() 是什么关系呢?,通过关系图谱,FutureTask 继承了 RunnableFuture,RunnableFuture 继承了 Runnable 和 Future:,所以,FutureTask 也是个 Runnable !!!,这里就有点意思了,既然 FutureTask 是个 Runnable,肯定就需要实现 FutureTask.run() 方法,那么 FutureTask 也可以作为 Thread 的初始化入参,使用姿势如下:,所以当执行 Thread.run() 时,其实是执行的 FutureTask.run(),这个是我们破解的第一层。,下面我们再破解 FutureTask.run() 和 Callable.call() 的关系。,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 给忘了~~,为什么要有 Future 呢?我再问一个问题,大家可能就知道了。,我们通过 FutureTask,借助 Thread 执行线程后,结果数据我们怎么获取到呢?这里就需要借助到 Future。,我们看一下 Future 接口:,对于 FutureTask,Callable 就是他的任务,而 FutureTask 内部维护了一个任务状态,所有的状态都是围绕这个任务来进行的,随着任务的进行,状态也在不断的更新。,FutureTask 继承了 Future,实现对任务的取消、数据获取、任务状态判断等功能。,比如我们经常会调用 get() 方法获取数据,如果任务没有执行完成,会将当前线程放入阻塞队列等待,当任务执行完后,会唤醒阻塞队列中的线程。,这个示例很简单:,这个示例其实不太适合线上的场景,因为每次调用都会初始化线程,如果调用过多,内存可能会被撑爆,需要借助线程池。
© 版权声明
文章版权归作者所有,未经允许请勿转载。