线程(二)OpenJDK 17 中线程启动的完整流程用C++ 源码详解之主-子线程通信机制

深入解析OpenJDK 17中Java线程的创建与主-子线程通信机制

引言

在Java中,线程的创建与启动通过Thread.start()实现,但底层是JVM与操作系统协作完成的复杂过程。本文基于OpenJDK 17的C++源码,揭秘Java线程创建时主线程与子线程的通信机制,分析JVM如何通过同步原语(如Monitor)实现线程状态的安全切换。


一、Java线程创建的核心流程

1. Java层到JVM层的调用链

当调用Thread.start()时,JVM最终会执行JVM_StartThread函数(位于jvm.cpp),核心步骤如下:

cpp

复制

下载

JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))// 1. 检查线程状态,防止重复启动if (java_lang_Thread::thread(jthread) != NULL) {// 抛出IllegalThreadStateException}// 2. 创建JavaThread对象,分配OS线程资源JavaThread* native_thread = new JavaThread(&thread_entry, stack_size);// 3. 初始化线程并启动Thread::start(native_thread);
JVM_END
2. OS线程的创建(以Linux为例)

os::create_thread中,JVM通过pthread_create创建操作系统线程:

cpp

复制

下载

bool os::create_thread(Thread* thread, ThreadType type, size_t stack_size) {// 分配OSThread对象OSThread* osthread = new OSThread();thread->set_osthread(osthread);// 初始化线程属性(栈大小、Guard Page等)pthread_attr_t attr;pthread_attr_init(&attr);pthread_attr_setstacksize(&attr, adjusted_stack_size);// 创建子线程,入口函数为thread_native_entryint ret = pthread_create(&tid, &attr, thread_native_entry, thread);// 等待子线程初始化完成Monitor* sync = osthread->startThread_lock();while (osthread->get_state() == ALLOCATED) {sync->wait_without_safepoint_check();}
}

二、主线程与子线程的同步机制

1. 关键对象:Monitor与OSThread
  • OSThread:每个线程的JVM内部表示,保存操作系统线程的元数据(如pthread_id、状态等)。

  • Monitor:JVM实现的同步锁,通过osthread->startThread_lock()获取,用于协调主线程与子线程。

2. 同步过程详解
主线程的等待

cpp

复制

下载

// 主线程代码(在os::create_thread中)
Monitor* sync = osthread->startThread_lock();
MutexLocker ml(sync);
while (osthread->get_state() == ALLOCATED) {sync->wait_without_safepoint_check(); // 阻塞等待子线程信号
}
  • 主线程在创建子线程后,通过wait进入阻塞状态,等待子线程初始化完成。

子线程的初始化与通知

子线程入口函数thread_native_entry中:

cpp

复制

下载

static void* thread_native_entry(Thread* thread) {OSThread* osthread = thread->osthread();Monitor* sync = osthread->startThread_lock();// 初始化线程状态、栈、TLS等osthread->set_thread_id(os::current_thread_id());PosixSignals::hotspot_sigmask(thread);// 通知主线程{MutexLocker ml(sync);osthread->set_state(INITIALIZED);sync->notify_all(); // 唤醒主线程}// 继续执行线程任务thread->call_run();
}
  • 子线程完成初始化后,通过notify_all()唤醒主线程。


三、核心源码解析

1. 状态机与竞态避免
  • 线程状态流转
    ALLOCATED → INITIALIZED → RUNNABLE
    主线程通过检查osthread->get_state()确保子线程就绪后再继续。

2. 为何需要wait_without_safepoint_check
  • 安全点(Safepoint):JVM在进行垃圾回收时,所有线程必须到达安全点。

  • wait_without_safepoint_check:在等待期间禁用安全点检查,防止死锁(若等待时触发GC,主线程无法响应)。

3. 错误处理与资源释放

若线程创建失败(如内存不足):

cpp

复制

下载

if (native_thread->osthread() == NULL) {native_thread->smr_delete();THROW_MSG(OutOfMemoryError, "Cannot create native thread");
}
  • JVM清理已分配的资源(如JavaThread对象),并抛出OOM异常。


四、通信机制的意义

  1. 线程安全性:确保子线程完全初始化后,主线程才将其加入线程列表。

  2. 资源一致性:避免子线程未就绪时被误调度。

  3. 跨平台抽象:通过Monitor统一不同操作系统的线程同步细节。


五、总结与启示

  • 同步的本质:主线程与子线程通过Monitor的等待-通知模型实现状态同步。

  • 性能优化wait_without_safepoint_check避免了安全点开销,提高线程创建效率。

  • 资源管理:JVM严格处理线程创建失败场景,防止内存泄漏。

通过分析OpenJDK源码,我们不仅理解了Java线程的创建机制,更看到JVM如何通过精巧的同步设计,在复杂性与性能之间取得平衡。

##源码

JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))JavaThread *native_thread = NULL;// We cannot hold the Threads_lock when we throw an exception,// due to rank ordering issues. Example:  we might need to grab the// Heap_lock while we construct the exception.bool throw_illegal_thread_state = false;// We must release the Threads_lock before we can post a jvmti event// in Thread::start.{// Ensure that the C++ Thread and OSThread structures aren't freed before// we operate.MutexLocker mu(Threads_lock);// Since JDK 5 the java.lang.Thread threadStatus is used to prevent// re-starting an already started thread, so we should usually find// that the JavaThread is null. However for a JNI attached thread// there is a small window between the Thread object being created// (with its JavaThread set) and the update to its threadStatus, so we// have to check for thisif (java_lang_Thread::thread(JNIHandles::resolve_non_null(jthread)) != NULL) {throw_illegal_thread_state = true;} else {// We could also check the stillborn flag to see if this thread was already stopped, but// for historical reasons we let the thread detect that itself when it starts runningjlong size =java_lang_Thread::stackSize(JNIHandles::resolve_non_null(jthread));// Allocate the C++ Thread structure and create the native thread.  The// stack size retrieved from java is 64-bit signed, but the constructor takes// size_t (an unsigned type), which may be 32 or 64-bit depending on the platform.//  - Avoid truncating on 32-bit platforms if size is greater than UINT_MAX.//  - Avoid passing negative values which would result in really large stacks.NOT_LP64(if (size > SIZE_MAX) size = SIZE_MAX;)size_t sz = size > 0 ? (size_t) size : 0;native_thread = new JavaThread(&thread_entry, sz);// At this point it may be possible that no osthread was created for the// JavaThread due to lack of memory. Check for this situation and throw// an exception if necessary. Eventually we may want to change this so// that we only grab the lock if the thread was created successfully -// then we can also do this check and throw the exception in the// JavaThread constructor.if (native_thread->osthread() != NULL) {// Note: the current thread is not being used within "prepare".native_thread->prepare(jthread);}}}if (throw_illegal_thread_state) {THROW(vmSymbols::java_lang_IllegalThreadStateException());}assert(native_thread != NULL, "Starting null thread?");if (native_thread->osthread() == NULL) {// No one should hold a reference to the 'native_thread'.native_thread->smr_delete();if (JvmtiExport::should_post_resource_exhausted()) {JvmtiExport::post_resource_exhausted(JVMTI_RESOURCE_EXHAUSTED_OOM_ERROR | JVMTI_RESOURCE_EXHAUSTED_THREADS,os::native_thread_creation_failed_msg());}THROW_MSG(vmSymbols::java_lang_OutOfMemoryError(),os::native_thread_creation_failed_msg());}#if INCLUDE_JFRif (Jfr::is_recording() && EventThreadStart::is_enabled() &&EventThreadStart::is_stacktrace_enabled()) {JfrThreadLocal* tl = native_thread->jfr_thread_local();// skip Thread.start() and Thread.start0()tl->set_cached_stack_trace_id(JfrStackTraceRepository::record(thread, 2));}
#endifThread::start(native_thread);JVM_ENDbool os::create_thread(Thread* thread, ThreadType thr_type,size_t req_stack_size) {assert(thread->osthread() == NULL, "caller responsible");// Allocate the OSThread objectOSThread* osthread = new OSThread(NULL, NULL);if (osthread == NULL) {return false;}// set the correct thread stateosthread->set_thread_type(thr_type);// Initial state is ALLOCATED but not INITIALIZEDosthread->set_state(ALLOCATED);thread->set_osthread(osthread);// init thread attributespthread_attr_t attr;pthread_attr_init(&attr);pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);// Calculate stack size if it's not specified by caller.size_t stack_size = os::Posix::get_initial_stack_size(thr_type, req_stack_size);// In glibc versions prior to 2.7 the guard size mechanism// is not implemented properly. The posix standard requires adding// the size of the guard pages to the stack size, instead Linux// takes the space out of 'stacksize'. Thus we adapt the requested// stack_size by the size of the guard pages to mimick proper// behaviour. However, be careful not to end up with a size// of zero due to overflow. Don't add the guard page in that case.size_t guard_size = os::Linux::default_guard_size(thr_type);// Configure glibc guard page. Must happen before calling// get_static_tls_area_size(), which uses the guard_size.pthread_attr_setguardsize(&attr, guard_size);size_t stack_adjust_size = 0;if (AdjustStackSizeForTLS) {// Adjust the stack_size for on-stack TLS - see get_static_tls_area_size().stack_adjust_size += get_static_tls_area_size(&attr);} else {stack_adjust_size += guard_size;}stack_adjust_size = align_up(stack_adjust_size, os::vm_page_size());if (stack_size <= SIZE_MAX - stack_adjust_size) {stack_size += stack_adjust_size;}assert(is_aligned(stack_size, os::vm_page_size()), "stack_size not aligned");int status = pthread_attr_setstacksize(&attr, stack_size);if (status != 0) {// pthread_attr_setstacksize() function can fail// if the stack size exceeds a system-imposed limit.assert_status(status == EINVAL, status, "pthread_attr_setstacksize");log_warning(os, thread)("The %sthread stack size specified is invalid: " SIZE_FORMAT "k",(thr_type == compiler_thread) ? "compiler " : ((thr_type == java_thread) ? "" : "VM "),stack_size / K);thread->set_osthread(NULL);delete osthread;return false;}ThreadState state;{pthread_t tid;int ret = pthread_create(&tid, &attr, (void* (*)(void*)) thread_native_entry, thread);char buf[64];if (ret == 0) {log_info(os, thread)("Thread started (pthread id: " UINTX_FORMAT ", attributes: %s). ",(uintx) tid, os::Posix::describe_pthread_attr(buf, sizeof(buf), &attr));} else {log_warning(os, thread)("Failed to start thread - pthread_create failed (%s) for attributes: %s.",os::errno_name(ret), os::Posix::describe_pthread_attr(buf, sizeof(buf), &attr));// Log some OS information which might explain why creating the thread failed.log_info(os, thread)("Number of threads approx. running in the VM: %d", Threads::number_of_threads());LogStream st(Log(os, thread)::info());os::Posix::print_rlimit_info(&st);os::print_memory_info(&st);os::Linux::print_proc_sys_info(&st);os::Linux::print_container_info(&st);}pthread_attr_destroy(&attr);if (ret != 0) {// Need to clean up stuff we've allocated so farthread->set_osthread(NULL);delete osthread;return false;}// Store pthread info into the OSThreadosthread->set_pthread_id(tid);// Wait until child thread is either initialized or aborted{Monitor* sync_with_child = osthread->startThread_lock();MutexLocker ml(sync_with_child, Mutex::_no_safepoint_check_flag);while ((state = osthread->get_state()) == ALLOCATED) {sync_with_child->wait_without_safepoint_check();}}}// The thread is returned suspended (in state INITIALIZED),// and is started higher up in the call chainassert(state == INITIALIZED, "race condition");return true;
}// Thread start routine for all newly created threads
static void *thread_native_entry(Thread *thread) {thread->record_stack_base_and_size();#ifndef __GLIBC__// Try to randomize the cache line index of hot stack frames.// This helps when threads of the same stack traces evict each other's// cache lines. The threads can be either from the same JVM instance, or// from different JVM instances. The benefit is especially true for// processors with hyperthreading technology.// This code is not needed anymore in glibc because it has MULTI_PAGE_ALIASING// and we did not see any degradation in performance without `alloca()`.static int counter = 0;int pid = os::current_process_id();int random = ((pid ^ counter++) & 7) * 128;void *stackmem = alloca(random != 0 ? random : 1); // ensure we allocate > 0// Ensure the alloca result is used in a way that prevents the compiler from eliding it.*(char *)stackmem = 1;
#endifthread->initialize_thread_current();OSThread* osthread = thread->osthread();Monitor* sync = osthread->startThread_lock();osthread->set_thread_id(os::current_thread_id());if (UseNUMA) {int lgrp_id = os::numa_get_group_id();if (lgrp_id != -1) {thread->set_lgrp_id(lgrp_id);}}// initialize signal mask for this threadPosixSignals::hotspot_sigmask(thread);// initialize floating point control registeros::Linux::init_thread_fpu_state();// handshaking with parent thread{MutexLocker ml(sync, Mutex::_no_safepoint_check_flag);// notify parent threadosthread->set_state(INITIALIZED);sync->notify_all();// wait until os::start_thread()while (osthread->get_state() == INITIALIZED) {sync->wait_without_safepoint_check();}}log_info(os, thread)("Thread is alive (tid: " UINTX_FORMAT ", pthread id: " UINTX_FORMAT ").",os::current_thread_id(), (uintx) pthread_self());assert(osthread->pthread_id() != 0, "pthread_id was not set as expected");// call one more level start routinethread->call_run();// Note: at this point the thread object may already have deleted itself.// Prevent dereferencing it from here on out.thread = NULL;log_info(os, thread)("Thread finished (tid: " UINTX_FORMAT ", pthread id: " UINTX_FORMAT ").",os::current_thread_id(), (uintx) pthread_self());return 0;
}void os::pd_start_thread(Thread* thread) {OSThread * osthread = thread->osthread();assert(osthread->get_state() != INITIALIZED, "just checking");Monitor* sync_with_child = osthread->startThread_lock();MutexLocker ml(sync_with_child, Mutex::_no_safepoint_check_flag);sync_with_child->notify();
}

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

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

相关文章

多线程爬虫语言选择与实现

之前文中有人提到&#xff1a;想要一个简单易用、能快速实现多线程爬虫的方案&#xff0c;而且目标是小网站&#xff0c;基本可以确定对反爬虫措施要求不高&#xff0c;这些就比较简单了。 以往我肯定要考虑常见的编程语言中哪些适合爬虫。Python、JavaScript&#xff08;Node…

AMD Vivado™ 设计套件生成加密比特流和加密密钥

概括 重要提示&#xff1a;有关使用AMD Vivado™ Design Suite 2016.4 及更早版本进行 eFUSE 编程的重要更新&#xff0c;请参阅AMD设计咨询 68832 。 本应用说明介绍了使用AMD Vivado™ 设计套件生成加密比特流和加密密钥&#xff08;高级加密标准伽罗瓦/计数器模式 (AES-GCM)…

Unity3D仿星露谷物语开发44之收集农作物

1、目标 在土地中挖掘后&#xff0c;洒下种子后逐渐成长&#xff0c;然后使用篮子收集成熟后的农作物&#xff0c;工具栏中也会相应地增加该农作物。 2、修改CropStandard的参数 Assets -> Prefabs -> Crop下的CropStandard&#xff0c;修改其Box Collider 2D的Size(Y…

list重点接口及模拟实现

list功能介绍 c中list是使用双向链表实现的一个容器&#xff0c;这个容器可以实现。插入&#xff0c;删除等的操作。与vector相比&#xff0c;vector适合尾插和尾删&#xff08;vector的实现是使用了动态数组的方式。在进行头删和头插的时候后面的数据会进行挪动&#xff0c;时…

CE17.【C++ Cont】练习题组17(堆专题)

目录 1.P2085 最小函数值 题目 分析 方法1:暴力求解 方法2:二次函数的性质(推荐!) 代码 提交结果 2.P1631 序列合并 分析 方法1:建两个堆 第一版代码 提交结果 第二版代码 提交结果 第三版代码 提交结果 方法2:只建一个堆 代码 提交结果 1.P2085 最小函数值…

题单:表达式求值1

题目描述 给定一个只包含 “加法” 和 “乘法” 的算术表达式&#xff0c;请你编程计算表达式的值。 输入格式 输入仅有一行&#xff0c;为需要计算的表达式&#xff0c;表达式中只包含数字、加法运算符 和乘法运算符 *&#xff0c;且没有括号。 所有参与运算的数字不超过…

DeepSeek超大模型的高效训练策略

算力挑战 训练DeepSeek此类千亿乃至万亿级别参数模型,对算力资源提出了极高要求。以DeepSeek-V3为例,其基础模型参数量为67亿,采用专家混合(MoE)架构后实际激活参数可达几百亿。如此规模的模型远超单张GPU显存容量极限,必须借助分布式并行才能加载和训练。具体挑战主要包…

MFC中DoDataExchange的简明指南

基本概念 DoDataExchange 是 MFC 框架中实现数据自动同步的核心函数&#xff0c;主要用于对话框中控件与成员变量的双向绑定。它能让控件中的数据和成员变量自动保持一致&#xff0c;无需手动读写控件数据。 使用示例 1&#xff09;变量声明 在对话框头文件中声明与控件对应…

FreeCAD源码分析: Transaction实现原理

本文阐述FreeCAD中Transaction的实现原理。 注1&#xff1a;限于研究水平&#xff0c;分析难免不当&#xff0c;欢迎批评指正。 注2&#xff1a;文章内容会不定期更新。 一、概念 Ref. from What is a Transaction? A transaction is a group of operations that have the f…

C++类与对象--1 特性一:封装

C面向对象三大特性&#xff1a; &#xff08;1&#xff09;封装&#xff1b;&#xff08;2&#xff09;继承&#xff1b;&#xff08;3&#xff09;多态&#xff1b; C认为万物皆是对象&#xff0c;对象上有对应的属性&#xff08;数据&#xff09;和行为&#xff08;方法&…

初探Reforcement Learning强化学习【QLearning/Sarsa/DQN】

文章目录 一、Q-learning现实理解&#xff1a;举例&#xff1a;回顾&#xff1a; 二、Sarsa和Q-learning的区别 三、Deep Q-NetworkDeep Q-Network是如何工作的&#xff1f;前处理&#xff1a;Convolution NetworksExperience Replay 一、Q-learning 是RL中model-free、value-…

WebRTC技术EasyRTC嵌入式音视频通信SDK打造远程实时视频通话监控巡检解决方案

一、方案概述​ 在现代工业生产、基础设施维护等领域&#xff0c;远程监控与巡检工作至关重要。传统的监控与巡检方式存在效率低、成本高、实时性差等问题。EasyRTC作为一种先进的实时音视频通信技术&#xff0c;具备低延迟、高稳定性、跨平台等特性&#xff0c;能够有效解决这…

专题四:综合练习(括号组合算法深度解析)

以leetcode22题为例 题目分析&#xff1a; 给一个数字n&#xff0c;返回合法的所有的括号组合 算法原理分析&#xff1a; 你可以先考虑如何不重不漏的罗列所有的括号组合 清楚什么是有效的括号组合&#xff1f;&#xff1f;&#xff1f; 1.所有的左括号的数量等于右括号的…

星云智控自定义物联网实时监控模板-为何成为痛点?物联网设备的多样化-优雅草卓伊凡

星云智控自定义物联网实时监控模板-为何成为痛点&#xff1f;物联网设备的多样化-优雅草卓伊凡 引言&#xff1a;物联网监控的模板革命 在万物互联的时代&#xff0c;设备监控已成为保障物联网系统稳定运行的核心环节。传统的标准化监控方案正面临着设备类型爆炸式增长带来的…

5.27本日总结

一、英语 复习list2list29 二、数学 学习14讲部分内容 三、408 学习计组1.2内容 四、总结 高数和计网明天结束当前章节&#xff0c;计网内容学完之后主要学习计组和操作系统 五、明日计划 英语&#xff1a;复习lsit3list28&#xff0c;完成07年第二篇阅读 数学&#…

几种运放典型应用电路

运算放大器简称:OP、OPA、OPAMP、运放。 一、电压跟随器 电压跟随器顾名思义运放的输入端电压与运放的输出电压相等 这个电路一般应用目的是增加电压驱动能力: 比如说有个3V电源,借一个负载,随着负载电流变大,3V就会变小说明3V电源带负载能力小,驱动能力弱,这个时候…

Android核心系统服务:AMS、WMS、PMS 与 system_server 进程解析

1. 引言 在 Android 系统中&#xff0c;ActivityManagerService (AMS)、WindowManagerService (WMS) 和 PackageManagerService (PMS) 是三个最核心的系统服务&#xff0c;它们分别管理着应用的生命周期、窗口显示和应用包管理。 但你是否知道&#xff0c;这些服务并不是独立…

从另一个视角理解TCP握手、挥手与可靠传输

本文将深入探讨 TCP 协议中三次握手、四次挥手的原理&#xff0c;以及其保证可靠传输的机制。 一、三次握手&#xff1a;为何是三次&#xff0c;而非两次&#xff1f; 建立 TCP 连接的过程犹如一场严谨的 “对话”&#xff0c;需要经过三次握手才能确保通信双方的可靠连接。 三…

将Docker compose 部署的夜莺V6版本升到V7版本的详细步骤、常见问题解答及相关镜像下载地址

环境说明 夜莺官网&#xff1a;首页 - 快猫星云Flashcat 夜莺安装程序下载地址&#xff1a;快猫星云下载中心 夜莺v7.7.2镜像&#xff08;X86架构&#xff09;&#xff1a; https://download.csdn.net/download/jjk_02027/90851161 夜莺ibex v1.2.0镜像&#xff08;X86架构…

JavaScript【4】数组和其他内置对象(API)

1.数组: 1.概述: js中数组可理解为一个存储数据的容器,但与java中的数组不太一样;js中的数组更像java中的集合,因为此集合在创建的时候,不需要定义数组长度,它可以实现动态扩容;js中的数组存储元素时,可以存储任意类型的元素,而java中的数组一旦创建后,就只能存储定义类型的元…