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

JVM源码剖析之线程的创建过程

说在前面:

对于Java线程的创建这个话题,似乎已经被"八股文"带偏~ 大部分Java程序员从"八股文"得知创建Java线程有N种方式,比如new Thread、new Runnable、Callable、线程池等等~ 而笔者写下这篇文章的目的是让大家从JVM源码的层面知道创建一个Java线程的方式。

版本信息:
jdk版本:jdk8u40

源码剖析:

public
class Thread implements Runnable {}

从Thread类的继承关系来看,Thread类实现Runnable接口,不少读者可能不明白为什么需要实现Runnable接口,也不明白Thread和Runnable之间的关系,那么笔者不妨以自己见解解释一番~

Thread:Java层面开发者使用的类,开发者可以使用它创建、启动、关闭线程等等操作

Runnable:一个函数式接口,无返回值、无入参,用于回调,其中编写开发者的回调逻辑(逻辑入口)

Callable:一个函数式接口,有返回值、无入参,用于回调,其中编写开发者的回调逻辑(逻辑入口)

所以Thread和Runnable之间的关系一目了然,一个是Java线程的API集合,一个是定义了逻辑入口的接口

为什么Thread需要实现Runnable,因为当线程启动后,Thread需要帮开发者自动执行线程执行体,所以需要一个执行入口方法,刚好Runnable接口就提供了此入口~

接下来看一下Thread的构造方法。

public Thread(Runnable target) {init(null, target, "Thread-" + nextThreadNum(), 0);}private void init(ThreadGroup g, Runnable target, String name,long stackSize) {init(g, target, name, stackSize, null, true);}private void init(ThreadGroup g, Runnable target, String name,long stackSize, AccessControlContext acc,boolean inheritThreadLocals) {if (name == null) {throw new NullPointerException("name cannot be null");}this.name = name;Thread parent = currentThread();SecurityManager security = System.getSecurityManager();if (g == null) {if (security != null) {g = security.getThreadGroup();}if (g == null) {g = parent.getThreadGroup();}}g.checkAccess();if (security != null) {if (isCCLOverridden(getClass())) {security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);}}g.addUnstarted();this.group = g;this.daemon = parent.isDaemon();this.priority = parent.getPriority();if (security == null || isCCLOverridden(parent.getClass()))this.contextClassLoader = parent.getContextClassLoader();elsethis.contextClassLoader = parent.contextClassLoader;this.inheritedAccessControlContext =acc != null ? acc : AccessController.getContext();this.target = target;setPriority(priority);if (inheritThreadLocals && parent.inheritableThreadLocals != null)this.inheritableThreadLocals =ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);this.stackSize = stackSize;tid = nextThreadID();}

构造方法虽然比较长,但是都是对当前线程的属性赋值,并没有其他任何操作,所以笔者连注释都没有写。

写到这里,我们需要理解一个点,Java是没有能力创建底层的线程,这个需要交给JVM来创建,而这里new Thread创建的仅仅是Java层面表示的线程对象~

而JVM来创建线程,所以需要Java层面调用native方法,所以我们看到Thread中start方法。

public synchronized void start() {// 此线程的状态如果不是创建状态就直接抛出非法逻辑异常。if (threadStatus != 0)throw new IllegalThreadStateException();// 添加到对应的线程组中。group.add(this);boolean started = false;try {// Native方法,此方法会创建底层的线程,并且启动线程,并且执行线程的执行体(Runnable的run方法)start0();started = true;		// 正常启动完毕。} finally {try {if (!started) {group.threadStartFailed(this);}} catch (Throwable ignore) {}}}private native void start0();

start方法会去底层创建线程,并且启动线程,执行线程的执行体 ,所以需要看到start0这个native方法的实现,所以下面是C/C++的代码~

{"start0",           "()V",        (void *)&JVM_StartThread}JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))JVMWrapper("JVM_StartThread");JavaThread *native_thread = NULL;bool throw_illegal_thread_state = false;{MutexLocker mu(Threads_lock);// 获取到Java开发者设置的栈大小,这个参数一般不会设置jlong size =java_lang_Thread::stackSize(JNIHandles::resolve_non_null(jthread));size_t sz = size > 0 ? (size_t) size : 0;// 创建底层的线程。// 当创建好线程后,会回调thread_entry此方法。native_thread = new JavaThread(&thread_entry, sz);if (native_thread->osthread() != NULL) {// 初始化线程,比如是否是守护线程,线程优先级。以及线程数量的统计。native_thread->prepare(jthread);}}// 设置成运行中状态Thread::start(native_thread);JVM_END

因为JVM是c/c++编写,而c++是有面向对象的思想存在,所以在new JavaThread中,会执行构造方法,我们看到构造方法。

JavaThread::JavaThread(ThreadFunction entry_point, size_t stack_sz) :Thread()	// 调用父类构造方法
{// 初始化参数initialize();_jni_attach_state = _not_attaching_via_jni;// 设置执行入口,当底层线程创建好以后,会去回调此方法set_entry_point(entry_point);		// 设置此线程的类型,当前线程是java_thread类型os::ThreadType thr_type = os::java_thread;thr_type = entry_point == &compiler_thread_entry ? os::compiler_thread :os::java_thread;// 真正的线程只有操作系统才有权利去创建,所以这里调用操作系统类库去创建。os::create_thread(this, thr_type, stack_sz);_safepoint_visible = false;}bool os::create_thread(Thread* thread, ThreadType thr_type, size_t stack_size) {assert(thread->osthread() == NULL, "caller responsible");OSThread* osthread = new OSThread(NULL, NULL);osthread->set_thread_type(thr_type);osthread->set_state(ALLOCATED);thread->set_osthread(osthread);pthread_attr_t attr;pthread_attr_init(&attr);pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);…………	// 省略一部分设置参数代码// glibc guard pagepthread_attr_setguardsize(&attr, os::Linux::default_guard_size(thr_type));ThreadState state;{pthread_t tid;// java_start 作为统一的入口,后续再根据thread类型做分发(多态)。int ret = pthread_create(&tid, &attr, (void* (*)(void*)) java_start, thread);pthread_attr_destroy(&attr);osthread->set_pthread_id(tid);// Wait until child thread is either initialized or aborted{Monitor* sync_with_child = osthread->startThread_lock();MutexLockerEx ml(sync_with_child, Mutex::_no_safepoint_check_flag);while ((state = osthread->get_state()) == ALLOCATED) {sync_with_child->wait(Mutex::_no_safepoint_check_flag);}}if (lock) {os::Linux::createThread_lock()->unlock();}}return true;
}

因为JVM是帮Java开发者实现了跨平台机制,所以需要适配所有OS平台,而我们只关心Linux操作系统,而Linux操作系统使用的是POSIX的线程标准,实现者肯定是Glibc类库,实现就是PThread线程库,而这里也是调用了pthread_create库函数创建了底层的线程(这里不懂没关系,只需要明白会去操作系统创建线程),并且设置线程的执行入口是java_start方法。

// pthread线程回调执行点。
static void *java_start(Thread *thread) {…………  // 省略大部分的设置参数和状态值的代码,方便观看核心源码// 这里是区分JVM层面抽象的不同线程的不同执行点(多态)// 而这里thread对象是JavaThreadthread->run();return 0;
}void JavaThread::run() {………… // 省略大部分的设置参数和状态值的代码,方便观看核心源码// 执行设置的入口thread_main_inner();
}

这里是pthread线程启动后回调的方法,这里会回调JavaThread的thread_main_inner方法。这里如果能看懂C++的读者会发现,这里使用多态思想非常完美~

void JavaThread::thread_main_inner() {// 不存在异常if (!this->has_pending_exception() &&!java_lang_Thread::is_stillborn(this->threadObj())) {HandleMark hm(this);// 回调设置的入口this->entry_point()(this, this);}DTRACE_THREAD_PROBE(stop, this);// 执行到这里代表线程执行结束了,需要做释放工作。this->exit(false);delete this;
}

 经过一系列的回调,最终来到thread_entry方法。

static void thread_entry(JavaThread* thread, TRAPS) {HandleMark hm(THREAD);Handle obj(THREAD, thread->threadObj());JavaValue result(T_VOID);JavaCalls::call_virtual(&result,obj,KlassHandle(THREAD, SystemDictionary::Thread_klass()),vmSymbols::run_method_name(),vmSymbols::void_method_signature(),THREAD);
}

这里非常的简单,在JVM层面调用Java的方法,而这里调用的就是Thread类中run方法(也即调用Runnable接口的run方法)

@Override
public void run() {// target参数往往是开发者传入的Runnable接口的实现类。if (target != null) {target.run();        // 调用开发者的逻辑}
}

上面的流程对于不懂JVM源码和不懂C++语言的读者来说非常吃力,甚至看不懂,这也很正常,源码层面是这样。但是读者会给你们做一个总结:

  1. 在Java层面调用Thread类的start方法
  2. start方法是一个native方法,会在c++层面创建JavaThread对象(因为c++也是面向对象)
  3. 在JavaThread的构造方法中会去创建OsThread对象(因为JVM是跨平台,存在很多个操作系统平台,所以需要一个OsThread做高度抽象)
  4. 在Linux操作系统平台中会去创建pthread线程(此线程可以理解为就是操作系统层面的线程)
  5. 创建完pthread线程后会经过一层一层的回调方法,最终回调thread_entry方法
  6. thread_entry方法中,会从JVM层面调用Java层面的方法,而调用的方法是Thread类中run方法,而Thread类是实现Runnable接口,所以也证明了文章开头说的Runnable 接口是作为一个执行入口
  7. 在Thread的run方法中,会去执行用户传入的Runnable接口,或者开发者重写了Thread的run方法。总之,这里就是回调开发者的逻辑。
  8. 所以Java的Thread创建入口就只有一个,就是Java的Thread类的start方法。

线程的关系图如上所述。

其实,你会发现,没有什么是中间抽一层不能解决的,每一层有每一层的职责,但是往往抽一层就需要有中间层的表示状态~

总结:

因为是深入到JVM源码层面,所以部分代码很多读者看不懂。但是代码是科学,他并不是神学,要论证真实性,必须要深入到源码层面。

相关文章:

JVM源码剖析之线程的创建过程

说在前面: 对于Java线程的创建这个话题,似乎已经被"八股文"带偏~ 大部分Java程序员从"八股文"得知创建Java线程有N种方式,比如new Thread、new Runnable、Callable、线程池等等~ 而笔者写下这篇文…...

ansible的介绍安装与模块

目录 一、ansible简介 二、ansible特点 三、Ansible核心组件与工作原理 1、核心组件 2、工作原理 四、ansible的安装 五、ansible 命令行模块 1.command 模块 2.shell 模块 3.cron 模块 4.user 模块 5.group 模…...

el-form简单封装一个列表页中的搜索栏

父组件如何使用 代码中注释很多, 应该很容易理解 <template><div><wgySearchv-model"searchDefault":fields"searchFields"reset"reset"submit"submit"><!-- 通过 slot 自定义的组件 传啥都行 --><te…...

【Python 2】列表 模式匹配 循环 dict set 可变对象与不可变对象

Python内置的一种数据类型是列表&#xff1a;list 变量classmates就是一个list。用len()函数可以获得list元素的个数 用索引来访问list中每一个位置的元素 当索引超出了范围时&#xff0c;Python会报一个IndexError错误&#xff0c;所以&#xff0c;要确保索引不要越界&#xf…...

深度学习—cv动物/植物数据集

文章目录 动物相关植物相关 动物相关 Edinburgh Pig Behavior Video Dataset:https://homepages.inf.ed.ac.uk/rbf/PIGDATA/ WLD 动物目标检测数据集: https://github.com/hellock/WLD 猪脸识别&#xff1a;https://blog.51cto.com/u_15404184/5289690 AFD动物面部数据集&…...

高效团队协作软件推荐:提升工作效率的优选方案!

使用团队协作软件有什么好处&#xff1f;可以摆脱过时的电子表格&#xff0c;有了单一的真实来源&#xff0c;您可以随时检查任何任务并获得可用的最新信息。 一目了然地查看所有正在进行的工作&#xff0c;看板式面板、甘特图和燃尽图等可视化工具可让您随时轻松获得项目的高级…...

Mac中使用virtualenv和virtualenvwrapper

Virtualenv 介绍 在使用 Python 开发的过程中&#xff0c;工程一多&#xff0c;难免会碰到不同的工程依赖不同版本的库的问题&#xff1b;亦或者是在开发过程中不想让物理环境里充斥各种各样的库&#xff0c;引发未来的依赖灾难。 因此&#xff0c;我们需要对于不同的工程使…...

wpf webBrowser控件 常用的函数和内存泄漏问题

介绍 WebBrowsers可以让我们在窗体中进行导航网页。 WebBrowser控件内部使用ie的引擎&#xff0c;因此使用WebBrowser我们必须安装ie浏览器&#xff08;windows默认安装的&#xff09;。 使用 直接在xmal中使用webBrowser控件 <WebBrowser x:Name"WebBrowser1"…...

AI游戏设计的半年度复盘;大模型+智能音箱再起波澜;昇思大模型技术公开课第2期;出海注册经验分享;如何使用LoRA微调Llama 2 | ShowMeAI日报

&#x1f440;日报&周刊合集 | &#x1f3a1;生产力工具与行业应用大全 | &#x1f9e1; 点赞关注评论拜托啦&#xff01; &#x1f525; 进步or毁灭&#xff1a;Nature 调研显示 1600 科学家对AI的割裂态度 国际顶级期刊 Nature 最近一项调研很有意思&#xff0c;全球 160…...

多线程 - 锁策略 CAS

常见的锁策略 此处谈到的锁策略,不局限于 Java,C,Python,数据库,操作系统……但凡是涉及到锁,都是可以应用到下列的锁策略的 乐观锁 vs 悲观锁 锁的实现者,预测接下来锁冲突(锁竞争,两个线程针对一个对象加锁,产生阻塞等待了)的概率是大,还是不大,根据这个冲突的概率,来接下…...

VP记录——The 2021 CCPC Weihai Onsite

网址 2021CCPC威海 赛时过题与罚时 A.Goodbye, Ziyin! 签到题&#xff0c;队友写的 #include<bits/stdc.h> using namespace std; int cnt[10], de[1000010]; int main() {int n;cin >> n;for(int i 1; i < n; i) {int u, v;scanf("%d %d", &…...

JavaWeb---Servlet

1.Srvlet概述 Servlet是运行在java服务器端的程序&#xff0c;用于接收和响应来着客户端基于HTTP协议的请求 如果想实现Servlet的功能&#xff0c;可以通过实现javax。servlet。Servlet接口或者继承它的实现类 核心方法&#xff1a;service&#xff08;&#xff09;&#xf…...

英语——方法篇——单词——谐音法+拼音法——50个单词记忆

theatre&#xff0c;剧场&#xff0c;太后th吃eat热re食物&#xff0c;就去剧场了 loud dolphin&#xff0c;做do脸皮厚plh在。。。里 humid&#xff0c;hu湖mi米d的 blender&#xff0c;b爸lend借给er儿。 tragedy&#xff0c;tr土人...

35道Rust面试题

这套Rust面试题包括了填空题、判断题、连线题和编码题等题型。 选择题 1 &#xff0c;下面哪个是打印变量language的正确方法&#xff1f; A&#xff0c;println("{}", language); B&#xff0c;println(language); C&#xff0c;println!("{}", langu…...

01 时钟配置初始化,debug

1. 开启debug series&#xff0c;否则只能下载一次&#xff0c;再次下载要配置boot 2.f0外部时钟配置 h750 配置 实测可用...

Halcon我的基础教程(一)(我的菜鸟教程笔记)-halcon仿射变换(Affine Transformation)的探究与学习

目录 什么是仿射变换?仿射变换有哪些方式?任何仿射变换都能由以下基本变换构造而来:在Halocn中,仿射变换具有重要的作用,那我们本文章重点讨论仿射变换基础性知识。 使用Halcon中的重要算子,仿射变换一般解决步骤,案例应用会在以后的文章中我们重点解答与讨论。 我们首先…...

c++视觉---中值滤波处理

中值滤波&#xff08;Median Filter&#xff09;是一种常用的非线性平滑滤波方法&#xff0c;用于去除图像中的噪声。它不像线性滤波&#xff08;如均值滤波或高斯滤波&#xff09;那样使用权重来计算平均值或加权平均值&#xff0c;而是选择滤波窗口内的像素值中的中间值作为输…...

Edge使用猴油脚本实战(实验室安全考试系统刷在线时长——网站永久自动刷新)

介绍 篡改猴 (Tampermonkey) 是拥有 超过 1000 万用户 的最流行的浏览器扩展之一。它允许用户自定义并增强您最喜爱的网页的功能。用户脚本是小型 JavaScript 程序&#xff0c;可用于向网页添加新功能或修改现有功能。使用 篡改猴&#xff0c;您可以轻松在任何网站上创建、管理…...

Vue 中 KeepAlive 内置缓存使用

KeepAlive 介绍及使用场景 KeepAlive 是 vue 中的内置组件&#xff0c;当多个组件动态切换时可以对实例状态进行缓存&#xff0c;用法如下 <router-view v-slot"{ Component }"><keep-alive><component :is"Component" /></keep-al…...

语言模型编码中/英文句子格式详解

文章目录 前言一、Bert的vocab.txt内容查看二、BERT模型转换方法(vocab.txt)三、vocab内容与模型转换对比四、中文编码总结 前言 最近一直在学习多模态大模型相关内容&#xff0c;特别是图像CV与语言LLM模型融合方法&#xff0c;如llama-1.5、blip、meta-transformer、glm等大…...

【Node.js】路由

基础使用 写法一&#xff1a; // server.js const http require(http); const fs require(fs); const route require(./route) http.createServer(function (req, res) {const myURL new URL(req.url, http://127.0.0.1)route(res, myURL.pathname)res.end() }).listen…...

matlab 2ask 4ask 信号调制

1 matlab 2ask close all clear all clcL =1000;Rb=2822400;%码元速率 Fs =Rb*8; Fc=Rb*30;%载波频率 Ld =L*Fs/Rb;%产生载波信号 t =0:1/Fs:L/Rb;carrier&...

Python利用jieba分词提取字符串中的省市区(字符串无规则)

目录 背景库&#xff08;jieba&#xff09;代码拓展结尾 背景 今天的需求就是在一串字符串中提取包含&#xff0c;省、市、区&#xff0c;该字符串不是一个正常的地址;,如下字符串 "安徽省、浙江省、江苏省、上海市,冷运标快首重1kg价格xx元,1.01kg(含)-5kg(不含)续重价…...

MuLogin防关联浏览器帮您一键实现Facebook账号多开

导言&#xff1a; 在当今数字化时代&#xff0c;社交媒体应用程序的普及程度越来越高。Facebook作为全球最大的社交媒体平台之一&#xff0c;拥有数十亿的用户。然而&#xff0c;对于一些用户来说&#xff0c;只拥有一个Facebook账号可能无法满足他们的需求。有时&#xff0c;…...

【C语言】每日一题(半月斩)——day4

目录 选择题 1、设变量已正确定义&#xff0c;以下不能统计出一行中输入字符个数&#xff08;不包含回车符&#xff09;的程序段是&#xff08; &#xff09; 2、运行以下程序后&#xff0c;如果从键盘上输入 65 14<回车> &#xff0c;则输出结果为&#xff08; &…...

Are you sure you want to continue connecting (yes/no) 每次ssh进

Lunix scp等命令不需要输入yes确认方法_scp不需要确认-CSDN博客 方法一&#xff1a;连接时加入StrictHostKeyCheckingno ssh -o StrictHostKeyCheckingno root192.168.1.100 方法二&#xff1a;修改/etc/ssh/ssh_config配置文件&#xff0c;添加&#xff1a; StrictHostKeyC…...

网络与信息系统安全设计规范

1、总则 1.1、目的 为规范XXXXX单位信息系统安全设计过程&#xff0c;确保整个信息安全管理体系在信息安全设计阶段符合国家相关标准和要求&#xff0c;特制订本规范。 1.2、范围 本规范适用于XXXXX单位在信息安全设计阶段的要求和规范管理。 1.3、职责 网络安全与信息化…...

在Linux怎么用vim实现把一个文件里面的文本复制到另一个文件里面

2023年10月9日&#xff0c;周一下午 我昨天遇到了这个问题&#xff0c;但在网上没找到图文并茂的博客&#xff0c;于是我自己摸索出解决办法后&#xff0c;决定写一篇图文并茂的博客。 情景 假设现在我要用vim把file_transfer.cpp的内容复制到file_transfer.hpp里面 第一步 …...

CCAK—云审计知识证书学习

目录 一、CCAK云审计知识证书概述 二、云治理概述 三、云信任 四、构建云合规计划 <...

3.springcloudalibaba gateway项目搭建

文章目录 前言一、搭建gateway项目1.1 pom配置1.2 新增配置如下 二、新增server服务2.1 pom配置2.2新增测试接口如下 三、测试验证3.1 分别启动两个服务&#xff0c;查看nacos是否注册成功3.2 测试 总结 前言 前面已经完成了springcloudalibaba项目搭建&#xff0c;接下来搭建…...