当前位置: 首页 > 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;申请缓冲区&…...

MySQL 隔离级别:脏读、幻读及不可重复读的原理与示例

一、MySQL 隔离级别 MySQL 提供了四种隔离级别,用于控制事务之间的并发访问以及数据的可见性,不同隔离级别对脏读、幻读、不可重复读这几种并发数据问题有着不同的处理方式,具体如下: 隔离级别脏读不可重复读幻读性能特点及锁机制读未提交(READ UNCOMMITTED)允许出现允许…...

Opencv中的addweighted函数

一.addweighted函数作用 addweighted&#xff08;&#xff09;是OpenCV库中用于图像处理的函数&#xff0c;主要功能是将两个输入图像&#xff08;尺寸和类型相同&#xff09;按照指定的权重进行加权叠加&#xff08;图像融合&#xff09;&#xff0c;并添加一个标量值&#x…...

DBAPI如何优雅的获取单条数据

API如何优雅的获取单条数据 案例一 对于查询类API&#xff0c;查询的是单条数据&#xff0c;比如根据主键ID查询用户信息&#xff0c;sql如下&#xff1a; select id, name, age from user where id #{id}API默认返回的数据格式是多条的&#xff0c;如下&#xff1a; {&qu…...

【python异步多线程】异步多线程爬虫代码示例

claude生成的python多线程、异步代码示例&#xff0c;模拟20个网页的爬取&#xff0c;每个网页假设要0.5-2秒完成。 代码 Python多线程爬虫教程 核心概念 多线程&#xff1a;允许程序同时执行多个任务&#xff0c;提高IO密集型任务&#xff08;如网络请求&#xff09;的效率…...

如何在最短时间内提升打ctf(web)的水平?

刚刚刷完2遍 bugku 的 web 题&#xff0c;前来答题。 每个人对刷题理解是不同&#xff0c;有的人是看了writeup就等于刷了&#xff0c;有的人是收藏了writeup就等于刷了&#xff0c;有的人是跟着writeup做了一遍就等于刷了&#xff0c;还有的人是独立思考做了一遍就等于刷了。…...

算法笔记2

1.字符串拼接最好用StringBuilder&#xff0c;不用String 2.创建List<>类型的数组并创建内存 List arr[] new ArrayList[26]; Arrays.setAll(arr, i -> new ArrayList<>()); 3.去掉首尾空格...

wpf在image控件上快速显示内存图像

wpf在image控件上快速显示内存图像https://www.cnblogs.com/haodafeng/p/10431387.html 如果你在寻找能够快速在image控件刷新大图像&#xff08;比如分辨率3000*3000的图像&#xff09;的办法&#xff0c;尤其是想把内存中的裸数据&#xff08;只有图像的数据&#xff0c;不包…...

「全栈技术解析」推客小程序系统开发:从架构设计到裂变增长的完整解决方案

在移动互联网营销竞争白热化的当下&#xff0c;推客小程序系统凭借其裂变传播、精准营销等特性&#xff0c;成为企业抢占市场的利器。本文将深度解析推客小程序系统开发的核心技术与实现路径&#xff0c;助力开发者打造具有市场竞争力的营销工具。​ 一、系统核心功能架构&…...

ZYNQ学习记录FPGA(一)ZYNQ简介

一、知识准备 1.一些术语,缩写和概念&#xff1a; 1&#xff09;ZYNQ全称&#xff1a;ZYNQ7000 All Pgrammable SoC 2&#xff09;SoC:system on chips(片上系统)&#xff0c;对比集成电路的SoB&#xff08;system on board&#xff09; 3&#xff09;ARM&#xff1a;处理器…...

快速排序算法改进:随机快排-荷兰国旗划分详解

随机快速排序-荷兰国旗划分算法详解 一、基础知识回顾1.1 快速排序简介1.2 荷兰国旗问题 二、随机快排 - 荷兰国旗划分原理2.1 随机化枢轴选择2.2 荷兰国旗划分过程2.3 结合随机快排与荷兰国旗划分 三、代码实现3.1 Python实现3.2 Java实现3.3 C实现 四、性能分析4.1 时间复杂度…...