線程(二)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.pswp.cn/news/905972.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/905972.shtml
英文地址,請注明出處:http://en.pswp.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中的數組一旦創建后,就只能存儲定義類型的元…