当前位置: 首页 > news >正文

【并发基础】Java中线程的创建和运行以及相关源码分析

目录

一、线程的创建和运行

1.1 创建和运行线程的三种方法

1.2 三者之间的继承关系

二、Thread类和Runnable接口的区别

2.1 Runnable接口可以实现线程之间资源共享,而Thread类不能

2.2 实现Runnable接口相对于继承Thread类的优点

三、实现 Runnable 接口和实现 Callable 接口的区别

四、Thread类和Runnable接口关于启动线程的源码解析

4.1 实现方法

4.2 Thread.start()方法源码分析

4.3 Runnable.run()方法源码分析

4.4 总结


一、线程的创建和运行

1.1 创建和运行线程的三种方法

Java里的程序天生就是多线程的,那么有几种启动线程的方式? 

Java 线程创建有3种方式:

  1. 继承 Thread 类并且重写 run 方法
  2. 实现 Runnable接口的 run 方法
  3. 使用 Callable接口和FutureTask类方式

1.2 三者之间的继承关系

public class Thread implements Runnable {}

Thread类也是实现的Runnable接口。

单独说下 FutureTask 的方式,这种方式的本身也是实现了Runnable 接口的 run 方法,看它的继承结构就可以知道。

前两种方式都没办法拿到任务的返回结果,但是 Futuretask 方式可以。

由此我们知道了,其实这三种创建方法的根源,都是来源于Runnable接口,这三种方法往上层追溯都能追到Runnble接口。

二、Thread类和Runnable接口的区别

2.1 Runnable接口可以实现线程之间资源共享,而Thread类不能

实际上Thread类和Runnable接口之间在使用上也是有所区别的,如果一个类继承Thread类,就不适合于多个线程共享资源,而实现了Runnable接口,则可以方便的实现资源的共享。

由上文我们就可以知道,Thread类和Runnable接口最大的区别就是继承Thread类不能资源共享,而实现Runnable接口可以资源共享。 

为什么Runnable可以共享数据:

总结起来原因就是用Runnable接口的方法可以对两个不同的Thread类的构造方法传入相同的实现Runnable接口的对象,那么这两个不同的Thread线程类本质操控的是同一个Runnable接口的实现对象了,调用的也是同一个run()方法,自然这两个线程下就实现了共享同一个Runnable实现类中的数据了。

如果两个Thread类的构造方法传入不同的Runnable接口实现类,那么两个Thread线程对象操作的不是同一个Runnable实现类,两个线程也就不能共享数据了。

2.2 实现Runnable接口相对于继承Thread类的优点

可见,实现Runnable接口相对于继承Thread类来说,有如下显著的优势:

  • 适合多个相同程序代码的线程去处理同一资源的情况。
  • 可以避免由于Java的单继承特性带来的局限。
  • 增强了程序的健壮性,代码能够被多个线程共享,代码与数据是独立的。
  • 线程池只能放入实现 Runable 或 Callable 类线程,不能直接放入继承 Thread 的类

三、实现 Runnable 接口和实现 Callable 接口的区别

  1. Runnable 是自从 java1.1 就有了,而 Callable 是 1.5 之后才加上去的
  2. 实现 Callable 接口的任务线程能返回执行结果,而实现 Runnable 接口的任务线程不能返回结果
  3. Callable 接口的 call()方法允许抛出异常,而 Runnable 接口的 run()方法的异常只能在内部消化,不能继续上抛
  4. 加入线程池运行,Runnable 使用 ExecutorService 的 execute 方法,Callable 使用 submit 方法。注:Callable 接口支持返回执行结果,此时需要调用 FutureTask.get()方法实现,此方法会阻塞主线程直到获取返回结果,当不调用此方法时,主线程不会阻塞

四、Thread类和Runnable接口关于启动线程的源码解析

这里我们来讲一下Java中创建线程最经典的这两种方式,在底层源码是如何实现的。

4.1 实现方法

Java 中实现多线程有两种「基本方式」:继承 Thread 类和实现 Runnable 接口。从实现的编程手法来看,认为这是两种实现方式并无不妥。但是究其实现根源,这么讲其实并不准确。

其实多线程从根本上讲只有一种实现方式,就是实例化 Thread,并且提供其执行的 run 方法。无论你是通过继承 Thread还是实现 Runnable接口,最终都是重写或者实现了 run 方法。而你真正启动线程都是通过实例化 Thread,调用其 start 方法

来看下两种不同实现方式的例子:

1. 继承 Thread 方式:

public class MyThread extends Thread {  public void run() {  System.out.println("MyThread.run()");  }  
}  
MyThread myThread1 = new MyThread();  
MyThread myThread2 = new MyThread();  
myThread1.start();  
myThread2.start();  

2. 实现 Runnable 方式

public class MyThread extends OtherClass implements Runnable {  public void run() {  System.out.println("MyThread.run()");  }  
} 
MyThread myThread = new MyThread();  
Thread thread = new Thread(myThread);  
thread.start(); 

第一种方式中,MyThread 继承了 Thread 类,启动时调用的 start 方法,其实还是他父类 Thread 的 start 方法。并最终触发执行 Student 重写的 run 方法。

第二种方式中,MyThread 实现 Runnable 接口,将MyThread对象作为参数传递给 Thread 构造函数。接下来还是调用了 Thread 的 start 方法。最后则会触发传入的 Runnable 实现类的 run 方法。

两种方式都是创建 Thread 或者 Thread 的子类,通过 Thread 的 start 方法启动。唯一不同是第一种 run 方法实现在 Thread 子类中。第二种则是把 run 方法逻辑转移到 Runnable 的实现类中。线程启动后,第一种方式是 thread 对象运行自己的 run 方法逻辑,第二种方式则是调用 Runnable 实现的 run 方法逻辑。

相比较来说,第二种方式是更好的实践,原因如下:

  1. java 语言中只能单继承,通过实现接口的方式,可以让实现类去继承其它类。而直接继承 thread 就不能再继承其它类了;
  2. 线程控制逻辑在 Thread 类中,业务运行逻辑在 Runnable 实现类中。解耦更为彻底;
  3. 实现 Runnable 的实例,可以被多个线程共享并执行。而实现 thread 是做不到这一点的。

看到这里,你是不是很好奇,为什么程序中调用的是 Thread 的 start 方法,而不是 run 方法?为什么线程在调用 start 方法后会执行 run 方法的逻辑呢?接下来我们通过学习 start 方法的源代码来找到答案。

4.2 Thread.start()方法源码分析

Thread类的无参构造方法:

public Thread() {init(null, null, "Thread-" + nextThreadNum(), 0);
}

如果是直接创建Thread类对象,我们通过源码就能看出,传入到target是空。在这种情况下,我们需要在Thread的继承类中去覆写run()方法,这样在Thread类执行run()方法的时候,就是调用我们继承类中覆写的run()方法逻辑。

我们知道Thraed类的对象是不能直接调用run()方法的,那么它是如何调用run()方法的呢?下面我们接着来进行分析。

Thread类中start()方法源码:

public synchronized void start() {if (threadStatus != 0)throw new IllegalThreadStateException();    group.add(this);boolean started = false;try {        start0();        started = true;    } finally {try {if (!started) {                group.threadStartFailed(this);            }        } catch (Throwable ignore) {        }    }
}

这段代码足够简单,简单到没什么内容。主要逻辑如下:

  1. 检查线程的状态,是否可以启动;
  2. 把线程加入到线程 group 中;
  3. 调用了 start0 () 方法。

可以看到 Start 方法中最终调用的是 start0()方法,并不是 run 方法。那么我们再看 start0 方法源代码:

private native void start0();

什么也没有,因为 start0 是一个 native 方法,也称为 JNI(Java Native Interface)方法。JNI 方法是 Java和其它语言交互的方式。同样也是 Java代码和虚拟机交互的方式,虚拟机就是由 C++ 和汇编所编写。

由于 start0 是一个 native 方法,所以后面的执行会进入到 JVM 中。那么 run 方法到底是何时被调用的呢?这里似乎找不到答案了。

难道我们错过了什么?回过头来我们再看看 Start 方法的注解。其实读源代码的时候,要先读注解,否则直接进入代码逻辑,容易陷进去,出不来。原来答案就在 start 方法的注解里,我们可以看到:

/* 
* Causes this thread to begin execution; the Java Virtual Machine* calls the run method of this thread.* 
* The result is that two threads are running concurrently: the
* current thread (which returns from the call to the
* start method) and the other thread (which executes its
* run method).
*
*
* It is never legal to start a thread more than once.
* In particular, a thread may not be restarted once it has completed execution.
*/

最关键一句: the Java Virtual Machine calls the run method of this thread。由此我们可以推断出整个执行流程如下:

start 方法调用了 start0 方法,start0 方法在 JVM 中,start0 中的逻辑会调用 run 方法。

至此,我们已经分析清楚从线程创建到 run 方法被执行的逻辑。但是通过实现 Runnbale 的方式实现多线程时,Runnable 的 run 方法是如何被调用的呢?

4.3 Runnable.run()方法源码分析

我们先从 Thread 的构造函数入手。原因是 Runnable 的实现对象通过构造函数传入 Thread。

Thread类构造方法源码:

public Thread(Runnable target) {    init(null, target, "Thread-" + nextThreadNum(), 0);
}

可以看到 Runnable 实现作为 target 对象传递进来。再次调用了 init 方法,init 方法有多个重载,最终调用的是Thread类中的如下方法:

private void init(ThreadGroup g, Runnable target, String name, long stackSize) {Thread parent = currentThread();if (g == null) {g = parent.getThreadGroup();}g.addUnstarted();this.group = g;this.target = target;this.priority = parent.getPriority();this.daemon = parent.isDaemon();setName(name);init2(parent);/* Stash the specified stack size in case the VM cares */this.stackSize = stackSize;tid = nextThreadID();
}

此方法里有一行代码:

this.target = target;

 原来 target 是 Thread类的成员变量:

/* What will be run. */
private Runnable target;

此时,Thread 的 target 被设置为你实现业务逻辑的 Runnable 实现。

我们再看下Thread类的run 方法的代码:

@Override
public void run() {if (target != null) {        target.run();    }
}

看到这里是不是已经很清楚了,当你传入了 target时(target不为null),在执行Thread类的run()方法时其实会调用执行 target 的 run 方法。也就是执行你实现业务逻辑的方法,我们需要在实现Runnable接口的类中实现Runnable接口的run()方法。整体执行流程如下:

如果你是通过继承 Thread,重写 run 方法的方式实现多线程。那么在上图中的第三步执行的就是你重写的 run 方法。

我们回过头看看 Thread 类的定义:

public class Thread implements Runnable

原来 Thread 也实现了 Runnable 接口。怪不得 Thread 类的 run 方法上有 @Override 注解。所以继承 Thread类实现多线程,其实也相当于是实现 Runnable 接口的 run 方法。只不过此时,不需要再传入一个 Thread 类去启动。它自己已具备了 Thread 的功能,自己就可以运转起来。既然 Thread 类也实现了 Runnable 接口,那么 Thread 子类对象是不是也可以传入另外的 Thread 对象,让其执行自己的 run 方法呢?答案是可行的。

4.4 总结

以上对多线程的两种实现方式做了分析。在学习多线程的同时,我们也应该学习源代码中优秀的设计模式。Java 中多线程的实现采用了模板模式Thread 是模板对象,负责线程相关的逻辑,比如线程的创建、运行以及各种操作。而线程真正的业务逻辑则被剥离出来,交由 Runnable 的实现类去实现。线程操作和业务逻辑完全解耦,普通开发者只需要聚焦在业务逻辑实现

执行业务逻辑,是 Thread 对象的生命周期中的重要一环。这一步通过调用传入 Runnable 的 run 方法实现。Thread 线程整体逻辑就是一个模板,把其中一个步骤剥离出来由其他类实现,这就是模板模式。


相关文章:【并发基础】线程,进程,协程的详细解释
                  【操作系统】一篇文章带你快速搞懂用户态和内核态

相关文章:

【并发基础】Java中线程的创建和运行以及相关源码分析

目录 一、线程的创建和运行 1.1 创建和运行线程的三种方法 1.2 三者之间的继承关系 二、Thread类和Runnable接口的区别 2.1 Runnable接口可以实现线程之间资源共享,而Thread类不能 2.2 实现Runnable接口相对于继承Thread类的优点 三、实现 Runnable 接口和实现 Call…...

Spark Shuffle

Shuffle : 集群范围内跨节点、跨进程的数据分发 分布式数据集在集群内的分发,会引入大量的磁盘 I/O 与网络I/O在 DAG 的计算中,Shuffle 环节的执行性能是最差的 , 会消耗所有类型的硬件资源 (CPU、内存、磁盘、网络) Spark 2.0 后,将 Shuff…...

Linux/MacOS 生成双击可执行文件

双击可执行文件包含两种:终端shell脚本 Unix可执行文件 1.终端shell脚本 随意新建一个文件(可使用command键N,前提是有已打开的文件),输入shell格式的测试代码,比如: #! /bin/sh echo “h…...

Ubuntu三种拨号方法

1.宽带拨号(PPPoE) (1)打开连接。关闭以太网连接,打开有线连接设置,取消勾选“自动连接”选项。 (2)配置连接。在终端输入命令sudo pppoeconf,会看到一系列配置信息,包括用户名、密码,配置完成后会有一些提示信息&…...

Vue-router的引入和安装

什么是Vue-Router?Vue路由器是Vue.js的官方路由器,它与Vue.js核心深度集成,使用Vue轻松构建单页应用程序变得轻而易举。功能包括:嵌套路线映射动态路由模块化,基于组件的路由器配置路由参数,查询&#xff0…...

无线WiFi安全渗透与攻防(四)之kismet的使用

系列文章 无线WiFi安全渗透与攻防(一)之无线安全环境搭建 无线WiFi安全渗透与攻防(二)之打造专属字典 无线WiFi安全渗透与攻防(三)之Windows扫描wifi和破解WiFi密码 kismet 如果要进行无线网络渗透测试,则必须先扫描所有有效的无线接入点。刚好在Kali Linux中&am…...

2023新版PMP考试有哪些变化?

对于2022年很多事情也都在发生,疫情也都没有完全结束,基金会已经开始通知下一场考试了,很多人也会担心新的考纲会不会给自己带来难度,其实这次六月份的考试很多人都内心已经知道了结果,所以这里也详细说一下新考纲的改…...

P8074 [COCI2009-2010#7] SVEMIR 最小生成树

[COCI2009-2010#7] SVEMIR 题目描述 太空帝国要通过建造隧道来联通它的 NNN 个星球。 每个星球用三维坐标 (xi,yi,zi)(x_i,y_i,z_i)(xi​,yi​,zi​) 来表示,而在两个星球 A,BA,BA,B 之间建造隧道的价格为 min⁡{∣xA−xB∣,∣yA−yB∣,∣zA−zB∣}\min\{|x_A-x_…...

10种常见网站安全攻击手段及防御方法

在某种程度上,互联网上的每个网站都容易遭受安全攻击。从人为失误到网络罪犯团伙发起的复杂攻击均在威胁范围之内。 网络攻击者最主要的动机是求财。无论你运营的是电子商务项目还是简单的小型商业网站,潜在攻击的风险就在那里。 知己知彼百战不殆&…...

为什么我选择收费的AdsPower指纹浏览器?

在决定开始用指纹浏览器之前,龙哥让我们团队的运营小哥找了市面上很多产品去测试。最后,还是决定用AdsPower。每个人的使用感受都不一样,龙哥就说说几个用得顺手的几个点。一、指纹环境强大 双内核引擎 市面上指纹浏览器内核都是基于谷歌Chro…...

Java输入输出和数组

一、问答题 1. 如何声明和创建一个一维数组&#xff1f; Int i[]new int[3] 2. 如何访问数组的元素&#xff1f; Int a[]new int a[3] for (int x0;x<a.length;x){ System.out.print(i[x]); } System.out.println(); 3.数组下标的类型是什么&#xff1f;最小的下标是什…...

这些免费API帮你快速开发,工作效率杠杠滴

一、短信发送 短信的应用可以说是非常的广泛了&#xff0c;短信API也是当下非常热门的API~ 短信验证码&#xff1a;可用于登录、注册、找回密码、支付认证等等应用场景。支持三大运营商&#xff0c;3秒可达&#xff0c;99.99&#xff05;到达率&#xff0c;支持大容量高并发。…...

干货|最全PCB布线教程总结,14条PCB布线原则技巧,保姆级搞定PCB布线

1、坚持手动布线&#xff0c;慎用自动布线2、了解制造商的规格3、合适的走线宽度4、迹线之间留出足够的空间5、元器件放置6、保持模拟和数字走线分开7、接地层8、走线和安装孔留有足够的空间9、交替走线方向10、避免电容耦合11、放置散热孔和焊盘12、接地和电源走线13、利用丝印…...

编程快捷键和markdown语法小计

Data Structure FQA文章目录1.idea快捷键汇总2.markdown一些常用语法1.idea快捷键汇总 altenter  快捷生成变量 altInsert可以新建类&#xff0c;文件&#xff0c;get或set方法&#xff0c;此快捷键又名创造一切 编辑区和文件区的跳转。 alt 1  &#xff1a;编辑区跳转至…...

内网vCenter部署教程二,最全的了!

一、组网说明 vCenter组网最佳实践 每台服务器需要6个网口,需要三个分布式交换机,每个交换机分配2个物理网卡做冗余,分别做为管理网络、业务网络、高可用网络使用。另vsan网络和vmotion网络可以复用业务网络或管理网络,vcenter HA需要单独用一个网络。 二、创建管理网络…...

2023-3-2 刷题情况

迷宫 题目描述 这天, 小明在玩迷宫游戏。 迷宫为一个 nn 的网格图, 小明可以在格子中移动, 左上角为 (1,1), 右 下角 (n,n) 为终点。迷宫中除了可以向上下左右四个方向移动一格以外, 还有 m 个双向传送门可以使用, 传送门可以连接两个任意格子。 假如小明处在格子 (x1,y1)(…...

Docker SYS_ADMIN 权限容器逃逸

1.漏洞原理Docker容器不同于虚拟机&#xff0c;它共享宿主机操作系统内核。宿主机和容器之间通过内核命名空间&#xff08;namespaces&#xff09;、内核Capabilities、CGroups&#xff08;control groups&#xff09;等技术进行隔离。若启动docker容器时给主机一个--cap-addSY…...

【Kotlin】 yyyy-MM-dd HH:mm:ss 时间格式 时间戳 全面解读超详细

时间格式 时间格式(协议)描述gg时期或纪元。y不包含纪元的年份。不具有前导零。yy不包含纪元的年份。具有前导零。yyyy包含纪元的四位数的年份。M月份数字。一位数的月份没有前导零。MM月份数字。一位数的月份有一个前导零。MMM月份的缩写名称&#xff0c;在AbbreviatedMonthN…...

git repack多包使用及相关性能测试

1、git数据结构 git 中存在四种数据结构&#xff0c;即object包含四种&#xff0c;分别是tree对象、blob对象、commit对象、tag对象 1.1 blob对象 存储文件内容&#xff0c;内容是二进制的形式&#xff0c;通过SHA-1算法对文件内容和头信息进行计算得到key(文件名)。 如果一…...

QT获取dll库文件详细信息

一、需求背景获取软件下依赖的dll库的版本信息&#xff0c;如下图所示版本为1.0.7.1018二、实现方法2.1步骤windows下实现&#xff0c;基于version.lib(version.dll)提供的函数获取这些信息首先使用GetFileVersionInfoSizeA(W)获取VersionInfo的大小&#xff0c;申请缓冲区&…...

3天刷完2026最新Java高频面试题(1000 道附答案解析)

2026年金三银四一半儿快要过去了&#xff0c;总结了上半年各类 Java 面试题&#xff0c;初中级和中高级都有&#xff0c;包括 Java 基础&#xff0c;JVM 知识面试题库&#xff0c;开源框架面试题库&#xff0c;操作系统面试题库&#xff0c;多线程面试题库&#xff0c;Tcp 面试…...

人形机器人关节驱动技术深度解析:旋转执行器的设计与应用全景

1. 旋转执行器&#xff1a;人形机器人的动力核心 当你看到人形机器人灵活地行走、挥手甚至跳舞时&#xff0c;有没有想过是什么让它们的关节能够如此精准地运动&#xff1f;答案就藏在那些不起眼的旋转执行器里。这些看似简单的装置&#xff0c;实际上是人形机器人最关键的传动…...

ChatGPT API调用实战:从基础接入到生产环境优化指南

ChatGPT API调用实战&#xff1a;从基础接入到生产环境优化指南 作为一名开发者&#xff0c;在将ChatGPT这类强大的AI能力集成到自己应用中的过程中&#xff0c;我踩过不少坑。从最初的简单请求&#xff0c;到后来面对高并发、长对话、成本控制等生产级挑战&#xff0c;整个过…...

OpenClaw自动化邮件分类:GLM-4.7-Flash智能收件箱管理

OpenClaw自动化邮件分类&#xff1a;GLM-4.7-Flash智能收件箱管理 1. 为什么需要智能邮件管理 每天早晨打开邮箱&#xff0c;看到堆积如山的未读邮件总是让人头疼。重要客户的需求可能被埋没在促销广告中&#xff0c;团队协作的紧急邮件可能混在订阅通知里。作为一名长期被邮…...

TrollInstallerX终极指南:一键在iOS设备上安装TrollStore的完整教程

TrollInstallerX终极指南&#xff1a;一键在iOS设备上安装TrollStore的完整教程 【免费下载链接】TrollInstallerX A TrollStore installer for iOS 14.0 - 16.6.1 项目地址: https://gitcode.com/gh_mirrors/tr/TrollInstallerX 您是否一直在寻找一种简单可靠的方法&am…...

页游党必看!传奇、篮球、策略全都有,点击即玩

对于喜欢玩网页游戏的朋友来说&#xff0c;找一个靠谱、福利多、游戏全的平台太重要了&#xff01;不用下载、点击即玩&#xff0c;还能安心挂机不担心跑路&#xff0c;这样的平台才是真刚需&#xff5e; 今天就给大家安利一个深耕页游十余载的老牌平台——602游戏平台&#x…...

从MATLAB算法到MiniCPM-V-2_6模型:科学计算与AI的融合实践

从MATLAB算法到MiniCPM-V-2_6模型&#xff1a;科学计算与AI的融合实践 如果你经常和MATLAB打交道&#xff0c;可能会遇到这样的场景&#xff1a;跑完一个复杂的仿真&#xff0c;生成了几十张图表和一堆数据&#xff0c;然后需要花上半天时间&#xff0c;手动整理结果、撰写分析…...

Hunyuan-MT-7B在Win11系统下的高效部署与性能调优

Hunyuan-MT-7B在Win11系统下的高效部署与性能调优 最近腾讯开源的Hunyuan-MT-7B翻译模型挺火的&#xff0c;70亿参数就拿下了WMT2025比赛里31个语种中的30个第一&#xff0c;支持33种语言互译&#xff0c;包括一些少数民族语言和方言。性能这么强&#xff0c;很多朋友都想在本…...

TranslucentTB:Windows任务栏透明化工具,让桌面视觉体验焕然一新

TranslucentTB&#xff1a;Windows任务栏透明化工具&#xff0c;让桌面视觉体验焕然一新 【免费下载链接】TranslucentTB A lightweight utility that makes the Windows taskbar translucent/transparent. 项目地址: https://gitcode.com/gh_mirrors/tr/TranslucentTB …...

Python从入门到精通(03章):变量、数据类型与类型转换

Python从入门到精通&#xff08;第03章&#xff09;&#xff1a;变量、数据类型与类型转换 开头导语 这是本系列第03章。本文采用“知识点讲解 错误示例 正确写法 自测清单”的结构&#xff0c;目标是让你不仅能看懂&#xff0c;还能独立写出可运行代码。建议你边看边敲&…...