Thread、Runnable、Callable、Future ... 的关系?

Thread、Runnable、Callable、Future、FutureTask,你能详细讲出他们的内部关系么?这也是面试经常问到的问题。

1. Thread 和 Runnable

1.1 Thread

我们先看一下 Thread 最简单的使用姿势:

public class MyThread extends Thread {public MyThread(String name) {super(name);}@Overridepublic 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 {@Overridepublic 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 的关系:

  1. MyTask 继承 Runnable,并实现了 run() 方法;

  2. Thread 初始化,将 MyTask 作为自己的成员变量;

  3. Thread 执行 run() 方法,线程处于“就绪”状态;

  4. 等待 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 和 Future。

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>() {@Overridepublic 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,如任务执行完,返回falseboolean cancel(boolean mayInterruptIfRunning);// 判断任务是否被取消了,正常执行完不算被取消boolean isCancelled();// 判断任务是否已经执行完成,任务取消或发生异常也算是完成,返回trueboolean 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;
}

这个示例很简单:

  1. 先将数据按照 batchSize 分成 N 批;

  2. 启动 N 个线程,去执行任务;

  3. 通过 futureTask.get() 获取每个线程数据,并汇总输出。

这个示例其实不太适合线上的场景,因为每次调用都会初始化线程,如果调用过多,内存可能会被撑爆,需要借助线程池。

https://github.com/lml200701158/java-study/blob/master/src/main/java/com/java/parallel/share/MultiThreadProcess.javaicon-default.png?t=M666https://github.com/lml200701158/java-study/blob/master/src/main/java/com/java/parallel/share/MultiThreadProcess.java

··················END················

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/283550.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Brocade 光纤交换机常用命令

Brocade SAN交换机常用命令使用电脑连接Brocade SAN交换机常用命令使用电脑连接管理网口&#xff0c;默认IP地址为&#xff1a;192.168.0.1&#xff0c;掩码&#xff1a;255.255.255.0默认用户名&#xff1a;admin&#xff0c;默认密码password1. switchStatusShow查看交换机的…

EntityFramework6.X 之 Fulent

Fulent Fulent是配置领域模型类的另一个方法&#xff0c;它比DataAnnotations提供更多的配置&#xff0c;提供以下三种方法映射 Mappings To Database Model-Wide Mapping 设置默认架构&#xff0c;设置经典约束 Entity Mapping 映射单个或多个表格或架构&#xff0c;映射…

Visual Studio 2022 正式支持 .NET MAUI 开发

点击上方蓝字关注我们&#xff08;本文阅读时间&#xff1a;5分钟)我们很高兴地宣布 Visual Studio 2022 正式支持 .NET MAUI 开发。现在&#xff0c;您可以使用 .NET 更快地构建跨平台原生客户端应用程序&#xff0c;并将它们从单个代码库发布到 Android、iOS、macOS 和 Windo…

cxGrid 在 GridMode = True 模式下实现标题点击排序以及标题列过滤筛选!!!

最近使用 cxGrid 这个表格控件&#xff0c;发现加载十几万笔数据时加载特别慢&#xff0c;同时用 DBGridEh 加载来对比&#xff0c;发现速度差异很大&#xff0c; 原来是 cxGrid 的 GridView 默认 GridModeFalse &#xff0c;此时加载数据集时会设置每一列的属性&#xff08;例…

Docker学习笔记

简介 Docker 属于 Linux 容器的一种封装&#xff0c;提供简单易用的容器使用接口。它是目前最流行的 Linux 容器解决方案。 Docker 将应用程序与该程序的依赖&#xff0c;打包在一个文件里面。运行这个文件&#xff0c;就会生成一个虚拟容器。程序在这个虚拟容器里运行&#xf…

python访问数据库

1. python DB api简介 python DB api python访问数据库的统一接口规范&#xff0c;详细可参考https://www.python.org/dev/peps/pep-0249/python DB api中主要包括三个重要的对象 数据库连接对象 connection&#xff0c;数据库交互对象 cursor和数据库异常类 exceptions2. 使用…

错误:“filesystem“ 不是 “std“ 的成员

分析原因&#xff1a;应该项目是C版本问题 1、项目属性 → 配置属性 → 常规 → C语言标准 2、项目属性 → C/C → 语言 → C语言标准 3、项目属性 → C/C → 建议行 → 其它选项 → 添加&#xff1a;/Zc:__cplusplus

Blazor预研与实战

背景最近一直在搞一件事&#xff0c;就是熟悉Blazor&#xff0c;后期需要将Blazor真正运用到项目内。前期做了一些调研&#xff0c;包括但不限于Blazor知识学习组件库生态预研与现有SPA框架做比对与WebForm做比对自己动手做个演示项目最终的体验非常不错&#xff0c;功能全面。…

MySQL Internals Manual

http://dev.mysql.com/doc/internals/en/

导入工程后编译不过,报错: apply plugin: 'com.github.dcendents.android-maven'

编译时报错&#xff1a; Error:(2, 0) No service of type Factory apply plugin: com.github.dcendents.android-maven 原来是导入工程时修改了根目录下的build.gradle的android gradle版本。 源码gradle版本是1.1.3&#xff0c;导入工程后修改为2.2.2&#xff0c;修改gradle-…

并发编程10大坑,你踩过几个?

目录 前言 1. SimpleDateFormat线程不安全 2. 双重检查锁的漏洞 3. volatile的原子性 4. 死锁 4.1 缩小锁的范围 4.2 保证锁的顺序 5. 没释放锁 6. HashMap导致内存溢出 7. 使用默认线程池 8. Async注解的陷阱 9. 自旋锁浪费cpu资源 10. ThreadLocal用完没清空 前…

CAP理论与MongoDB一致性、可用性的一些思考

大约在五六年前&#xff0c;第一次接触到了当时已经是hot topic的NoSql。不过那个时候学的用的都是mysql&#xff0c;Nosql对于我而言还是新事物&#xff0c;并没有真正使用&#xff0c;只是不明觉厉。但是印象深刻的是这么一张图片&#xff08;后来google到图片来自这里&#…

十年老站吐血迁移实录

因为一些内容审查方面的原因&#xff0c;一个运行了很久的论坛被要求限期迁移出现有机房。管制很严&#xff0c;要求在1-2天内完成。论坛的文件&#xff08;主要是图片及附件&#xff09;100多G,数据库有80G。我评估了一下&#xff0c;导出数据&#xff0c;部署新环境&#xff…

【开源】一个WPF开发的XML记事本

今天推荐一个WPF开源项目&#xff1a;XmlNotepad[1]。此仓库由 WPF开发者[2] 推荐&#xff0c;站长简单翻译分享。介绍XML Notepad[3] 是一个 Windows 程序&#xff0c;它为浏览和编辑 XML 文档提供了一个简单直观的用户界面。有四种安装方式&#xff1a;ClickOnce 安装程序[4]…

log4j2 mybatis 显示 sql 和 结果集

为什么80%的码农都做不了架构师&#xff1f;>>> 首先说明版本&#xff1a; log4j 版本是 第2版beta9&#xff0c;引用jar包 log4j-api-2.0-beta9.jar log4j-core-2.0-beta9.jar mybatis 是 mybatis-3.2.3 log4j2 配置 如下&#xff0c;命名为 log4j2.xml&#xff0…

深入解析volatile关键字

目录 1. 初步认识volatile 2. volatile的特性一&#xff1a;保证可见性 2.1 volatile关键字是如何保证可见性的&#xff1f; 2.2 从JMM来看可见性 2.3 从硬件层面了解可见性的本质 2.3.1 CPU高速缓存 2.3.2 缓存一致性 2.3.3 MESI 协议 2.3.4 缓存一致性小结 3. vola…

Nginx基础配置

一、主配置文件结构main block&#xff1b;#全局块配置全局生效event{#事件驱动相关配置 }http{#http/https协议相关配置段 server { ... }&#xff1a;#每个server用于定义一个虚拟主机&#xff1b; server { ... server_name root alias location [OPERATOR] URL { ... if CO…

上周面试回来后写的Java面试总结,想进BAT必看

上周陪同之前一起工作的同事去面试&#xff08;乔治&#xff0c;小袁&#xff0c;鹏飞&#xff08;面试人&#xff09;&#xff09;&#xff0c;第一站是去深圳&#xff0c;第二站上海&#xff0c;第三站杭州。面试什么公司我在这里就不多说了&#xff0c;你们知道是一线公司就…

从贝叶斯方法谈到贝叶斯网络

从贝叶斯方法谈到贝叶斯网络0 引言其实。介绍贝叶斯定理、贝叶斯方法、贝叶斯判断的资料、书籍不少&#xff0c;比方《数理统计学简史》&#xff0c;以及《统计决策论及贝叶斯分析 James O.Berger著》等等&#xff0c;然介绍贝叶斯网络的中文资料则非常少。中文书籍总共也没几本…

k8s 读书笔记 - kubectl 命令行工具用法详解

kubectl 在 k8s 集群中作为 客户端 CLI 工具&#xff0c;可以让用户使用 kubectl 工具执行命令行&#xff0c;并通过使用 k8s API 与 k8s 集群的控制面&#xff08;kube-controller-manager&#xff09;进行通信。kubectl 语法格式kubectl 命令行的语法格式如下&#xff1a;kub…