【Java并发 03】常用API及底层实现——Thread.start
文章目录
龙云尧个人博客,转载请注明出处。 CSDN地址:http://blog.csdn.net/michael753951 个人blog地址:http://yaoyl.cn
知识储备
上一章节中我们讨论了Thread的状态转换,以及Thread状态和OS状态之间的映射关系。本章节开始进行发散,来看看常用的Thread操作相关的API有哪些。
本部分所有内容,JDK和JVM信息如下:
java version "15.0.1" 2020-10-20 Java(TM) SE Runtime Environment (build 15.0.1+9-18) Java HotSpot(TM) 64-Bit Server VM (build 15.0.1+9-18, mixed mode, sharing)
Oracle JDK 15从官网可以直接下载,HotSpot源码参见如下:
- HotSpot的开源项目位于:OpenJDK/JDK;
- 源代码仓库为:OpenJdk;
- 仓库中源代码对应JDK 15的TAG为
jdk-15-ga
; - 我的所有中文注释均上传在我个人fork出来的仓库中,参见YunyaoLong/jdk,提交分支为
jdk-15-ga-ch-cn
太长不看
线程的创建其实是JVM将操作系统的线程创建做了一次二次封装。
整体主要的线程创建的流程为:
- 计算线程所需要的内存空间;
- 向操作系统申请创建一个线程;
- 对该线程设置优先级;
- 将线程加入线程队列;
- 将线程的状态设置成RUNNABLE并且让操作系统尝试唤醒。
这由于不同的操作系统对于线程的优先级的设置的范围不一样,因此在代码开发过程中,不建议针对线程设置不同的优先级。
如果一定需要设置优先级,也建议尽可能地减少优先级的梯度,同时让这些优先级尽可能均匀的分散在java.lang.Thread#MIN_PRIORITY
和java.lang.Thread#MAX_PRIORITY
之间(通常对应的是1-10)。
或者翻阅源码或者相关操作系统手册,确定自己优先级设置能够正常映射到对应的优先级上,否则就可能出现无法预料的线程调度错乱问题。
native方法
在看线程创建之前,我们首先要知道什么是native方法。
我们都知道,在Java中我们的所有代码都是在JVM中运行的,JVM通过一次虚拟化,对上层代码屏蔽了下游OS和硬件的差异。但是我们在实际项目中又不可避免的需要对OS或者硬件做操作,比如操作文件,操作线程,获取键盘/摄像头输入等。
这时候就需要我们的JNI和native方法相互配合了。
Java定义了JNI(Java Native Interface, Java本地接口)用于Java程序片段和本地其他类型的语言进行交互和调用,所有的JNI定义完成后,均使用native
关键字进行修饰。JVM则封装了对应的native方法用于接口的具体调用。
本次Thread相关的native方法均为C++编写,因此如果之前完全没有学习过C++的同学,先稍微学习一下C++的基础知识再继续往下看。
线程创建
API
我们在上一章节中,知道了Thread的激活方法是java.lang.Thread#start
。
该方法调用完成之后,JVM就会帮我们创建一个线程,并将其状态标记为NEW状态,并且放进操作系统的就绪队列。
当该线程被从操作系统拉出来执行后,状态将变成RUN,并开始真正进行工作。
底层实现
JVM中的start0方法
通过跟踪源代码,我们可以发现线程的创建实际上是通过一个java.lang.Thread#start0
native方法具体实现的。
(这里其实可以发现start0仅会被start方法调用,也说明了线程的创建方式有且只有java.lang.Thread#start
)
通过代码追踪,我们首先可以找到start0的入口函数位置,位于src\java.base\share\native\libjava
中的Thread.c
库,在这个库中,HotSpo定义了一组关系:
1static JNINativeMethod methods[] = {
2 {"start0", "()V", (void *)&JVM_StartThread},
3 {"stop0", "(" OBJ ")V", (void *)&JVM_StopThread},
4 {"isAlive", "()Z", (void *)&JVM_IsThreadAlive},
5 {"suspend0", "()V", (void *)&JVM_SuspendThread},
6 {"resume0", "()V", (void *)&JVM_ResumeThread},
7 {"setPriority0", "(I)V", (void *)&JVM_SetThreadPriority},
8 {"yield", "()V", (void *)&JVM_Yield},
9 {"sleep", "(J)V", (void *)&JVM_Sleep},
10 {"currentThread", "()" THD, (void *)&JVM_CurrentThread},
11 {"interrupt0", "()V", (void *)&JVM_Interrupt},
12 {"holdsLock", "(" OBJ ")Z", (void *)&JVM_HoldsLock},
13 {"getThreads", "()[" THD, (void *)&JVM_GetAllThreads},
14 {"dumpThreads", "([" THD ")[[" STE, (void *)&JVM_DumpThreads},
15 {"setNativeName", "(" STR ")V", (void *)&JVM_SetNativeThreadName},
16};
因此我们的start0就对应了其中的JVM_StartThread
方法。
因此我们继续查阅到src/hotspot/share/include/jvm.h
库头,发现了其中的JVM_StartThread
方法。对应的,我们找到其实现类src\hotspot\share\prims\jvm.cpp
。
1JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))
2 JVMWrapper("JVM_StartThread");
3 JavaThread *native_thread = NULL;
4 // 线程操作的锁获取标记
5 bool throw_illegal_thread_state = false;
6
7 // 线程对象的空间申请代码块
8 {
9 // 尝试获取锁
10 MutexLocker mu(Threads_lock);
11
12 // 保证线程由于Thread.state为后期(Java 1.5)引入,因此需要确定state非null,防止查询Thread.state时出现空指针异常
13 if (java_lang_Thread::thread(JNIHandles::resolve_non_null(jthread)) != NULL) {
14 throw_illegal_thread_state = true;
15 } else {
16 // 计算线程的栈空间
17 jlong size = java_lang_Thread::stackSize(JNIHandles::resolve_non_null(jthread));
18 // 为native_thread申请内存空间(申请32位还是64位视操作系统决定,所以是使用size_t防止爆int)
19 NOT_LP64(if (size > SIZE_MAX) size = SIZE_MAX;)
20 size_t sz = size > 0 ? (size_t) size : 0;
21 native_thread = new JavaThread(&thread_entry, sz);
22
23 // 防止操作系统因为内存不足而导致native_thread创建失败
24 if (native_thread->osthread() != NULL) {
25 // 标记当前线程的状态为prepare
26 native_thread->prepare(jthread);
27 }
28 }
29 }
30
31 // 检查JNI传入的线程对象的状态,如果为空,就抛出异常
32 if (throw_illegal_thread_state) {
33 THROW(vmSymbols::java_lang_IllegalThreadStateException());
34 }
35
36 // 断言native_thread已经成功分配到对象空间
37 assert(native_thread != NULL, "Starting null thread?");
38
39 if (native_thread->osthread() == NULL) {
40 // 如果native_thread对象空间中,没有实际申请到OS线程,进行异常处理并且抛出异常
41 native_thread->smr_delete();
42 if (JvmtiExport::should_post_resource_exhausted()) {
43 JvmtiExport::post_resource_exhausted(
44 JVMTI_RESOURCE_EXHAUSTED_OOM_ERROR | JVMTI_RESOURCE_EXHAUSTED_THREADS,
45 os::native_thread_creation_failed_msg());
46 }
47 THROW_MSG(vmSymbols::java_lang_OutOfMemoryError(),
48 os::native_thread_creation_failed_msg());
49 }
50
51// 如果有加载Java Flight Recorder工具,针对JFR做特殊处理
52#if INCLUDE_JFR
53 if (Jfr::is_recording() && EventThreadStart::is_enabled() &&
54 EventThreadStart::is_stacktrace_enabled()) {
55 JfrThreadLocal* tl = native_thread->jfr_thread_local();
56 // skip Thread.start() and Thread.start0()
57 tl->set_cached_stack_trace_id(JfrStackTraceRepository::record(thread, 2));
58 }
59#endif
60
61 // 调用Thread::start方法开始进行线程操作
62 Thread::start(native_thread);
63
64JVM_END
其中有几个比较重要的函数:
- 线程对象创建函数
new JavaThread(&thread_entry, sz)
- OS线程查看入口
native_thread->osthread()
- 线程状态初始化函数
native_thread->prepare(jthread)
- 线程启动函数
Thread::start(native_thread)
为了深入理解这些函数,我们一个一个展开这些代码的具体实现。
JavaThread对象及构造函数
JavaThread
的结构定义位于src/hotspot/share/runtime/thread.hpp
。
其中定义的参数比较多,不建议挨个查看作用,整体和Thread的关系可以参考官方注释中的结构:
1// - Thread
2// - JavaThread
3// - various subclasses eg CompilerThread, ServiceThread
4// - NonJavaThread
5// - NamedThread
6// - VMThread
7// - ConcurrentGCThread
8// - WorkerThread
9// - GangWorker
10// - WatcherThread
11// - JfrThreadSampler
JavaThread构造函数的具体实现参见src/hotspot/share/runtime/thread.cpp
。
1JavaThread::JavaThread(ThreadFunction entry_point, size_t stack_sz) :
2 Thread() {
3 initialize(); // JavaThread对象的参数初始化,不建议细看
4 _jni_attach_state = _not_attaching_via_jni;
5 set_entry_point(entry_point);
6 // 定义需要创建的线程为java_thread
7 os::ThreadType thr_type = os::java_thread;
8 thr_type = entry_point == &compiler_thread_entry ? os::compiler_thread :
9 os::java_thread;
10 // 调用线程创建接口
11 os::create_thread(this, thr_type, stack_sz);
12 // 以下内容主要自官方注释:
13 // 当操作系统没有可分配的内存的时候,_osthread有可能返回NULL。
14 // 但是此时我们又不能抛出OOM Error,因为调用方此时可能持有了一把锁(参见上方的mu(Threads_lock)),这些锁必须在抛出异常前释放掉。如果我们需要抛出的异常,就需要为这个Exception对象申请内存,并初始化异常对象,此时可能已经没有足够内存了,这种行为就有可能锁回收失败。
15 // 因此建议JVM先释放所有的锁,然后Java代码中如果发现线程申请失败的话,自己抛出异常(也可以触发OOM Error)。(译者注: 然而好像目前主要的Java发行版,都没有针对这种场景抛出异常)
16 //
17 // 代码运行到这里的时候,线程依然保持挂起的状态。该线程必须由创建者(JVM)执行运行(start)动作。
18 // 此外,线程的创建者(JVM)需要调用Threads:add用于将创建之后的线程放入线程队列中,本处代码没有添加是因为每一个线程对象必须完全创建(而此处可能会创建失败)
19}
这里又涉及到了一个接口——os::create_thread(this, thr_type, stack_sz)
。我们继续深挖它。
os:create_thread方法
该方法的接口定义在src\hotspot\share\runtime\os.hpp
,在不同的操作系统中,有不同的实现,参见os_linux.cpp
、os_windows.cpp
、os_posix.cpp
、os_aix.cpp
、os_bsd.cpp
。
这里以linux系统中的实现os_linux.cpp
为例,linux中的线程创建,使用的是标准的pthread库。
(windows使用的是MSDN
的_beginthreadex
方法,具体实现可以自行跟踪源码,本次不做展开)
1bool os::create_thread(Thread* thread, ThreadType thr_type,
2 size_t req_stack_size) {
3 assert(thread->osthread() == NULL, "caller responsible");
4
5 // 创建一个OSThread对象
6 OSThread* osthread = new OSThread(NULL, NULL); // 调用pd_initialize()接口并初始化两个入参
7 if (osthread == NULL) {
8 return false;
9 }
10
11 // 设置线程类型(就是上方传入的java_thread)
12 osthread->set_thread_type(thr_type);
13
14 // 设置当前线程状态为已分配(ALLOCATED),但是还未初始化(INITIALIZED)
15 osthread->set_state(ALLOCATED);
16
17 thread->set_osthread(osthread);
18
19 // 调用pthread库初始化线程属性
20 pthread_attr_t attr;
21 pthread_attr_init(&attr);
22 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
23
24 // 申请req_stack_size大小的线程。
25 size_t stack_size = os::Posix::get_initial_stack_size(thr_type, req_stack_size);
26 // 在2.7和之前的glibc版本中,guard size的计算机制没有正确实现。
27 // posix标准要求线程的栈空间中包含将guard pages的诉求,而Linux创建线程的时候,线程栈大小(stacksize)并没有包含guard pages的诉求。
28 // 因此,JVM通过手工计算保护页面的大小来调整请求的stack_size,以模仿适当的行为。
29 // 但是,请注意最终计算出来的栈大小不要以0结尾,否则可能会出现栈溢出(overflow)。 在这种情况下,请勿不要添加guard pages的诉求。
30 size_t guard_size = os::Linux::default_guard_size(thr_type);
31 // 设置glibc库中的guard page属性。
32 // 由于get_static_tls_area_size()需要读取guard page属性,因此该方法必须在get_static_tls_area_size()之前调用。
33 pthread_attr_setguardsize(&attr, guard_size);
34
35 size_t stack_adjust_size = 0;
36 if (AdjustStackSizeForTLS) {
37 // 为线程本地存储(Thread Local Storage, TLS)预留资源,参见get_static_tls_area_size().
38 stack_adjust_size += get_static_tls_area_size(&attr);
39 } else {
40 stack_adjust_size += guard_size;
41 }
42
43 // 根据操作系统的分页大小,让栈的诉求向上对齐
44 stack_adjust_size = align_up(stack_adjust_size, os::vm_page_size());
45 if (stack_size <= SIZE_MAX - stack_adjust_size) {
46 stack_size += stack_adjust_size;
47 }
48 assert(is_aligned(stack_size, os::vm_page_size()), "stack_size not aligned");
49
50 int status = pthread_attr_setstacksize(&attr, stack_size);
51 assert_status(status == 0, status, "pthread_attr_setstacksize");
52
53 ThreadState state;
54
55 {
56 pthread_t tid;
57 // 调用pthread_create创建线程,同时获取线程创建结果(0成功,其余失败)
58 int ret = pthread_create(&tid, &attr, (void* (*)(void*)) thread_native_entry, thread);
59
60 char buf[64];
61 if (ret == 0) {
62 log_info(os, thread)("Thread started (pthread id: " UINTX_FORMAT ", attributes: %s). ",
63 (uintx) tid, os::Posix::describe_pthread_attr(buf, sizeof(buf), &attr));
64 } else {
65 log_warning(os, thread)("Failed to start thread - pthread_create failed (%s) for attributes: %s.",
66 os::errno_name(ret), os::Posix::describe_pthread_attr(buf, sizeof(buf), &attr));
67 // Log some OS information which might explain why creating the thread failed.
68 log_info(os, thread)("Number of threads approx. running in the VM: %d", Threads::number_of_threads());
69 LogStream st(Log(os, thread)::info());
70 os::Posix::print_rlimit_info(&st);
71 os::print_memory_info(&st);
72 os::Linux::print_proc_sys_info(&st);
73 os::Linux::print_container_info(&st);
74 }
75
76 pthread_attr_destroy(&attr);
77
78 if (ret != 0) {
79 // 如果创建失败,则释放osthread申请的内存空间
80 thread->set_osthread(NULL);
81 delete osthread;
82 return false;
83 }
84
85 // 将创建完成的操作系统线程id缓存到osthread对象中
86 osthread->set_pthread_id(tid);
87
88 // 如果自己的子线程还在初始化完成(处于ALLOCATED但是没有到INITIALIZED)
89 {
90 Monitor* sync_with_child = osthread->startThread_lock();
91 MutexLocker ml(sync_with_child, Mutex::_no_safepoint_check_flag);
92 while ((state = osthread->get_state()) == ALLOCATED) {
93 sync_with_child->wait_without_safepoint_check();
94 }
95 }
96 }
97
98 // 如果当前线程已经是一个僵尸进程(ZOMBIE),则手工释放osthread申请的内存空间
99 if (state == ZOMBIE) {
100 thread->set_osthread(NULL);
101 delete osthread;
102 return false;
103 }
104
105 // 线程创建完成,状态为INITIALIZED
106 assert(state == INITIALIZED, "race condition");
107 return true;
108}
到这里操作系统线程已经通过pthread库创建完成了。我们start0方法也终于执行到了native_thread = new JavaThread(&thread_entry, sz);
这一行。
osthread方法
这个暂未找到在哪实现,但是其实我们看上面osthread的创建代码,其实就很容易何以知道这个方法就是返回create_thread中
创建好的osthread
。
JavaThread::prepare方法
再次回到我们的JavaThread实现类中来,我们看看prepare代码的具体实现。同样还是以Linux为例(注,在上文我们知道Linux使用的是pthread库进行的创建,也即posix标准线程)。
1void JavaThread::prepare(jobject jni_thread, ThreadPriority prio) {
2
3 assert(Threads_lock->owner() == Thread::current(), "must have threads lock");
4 assert(NoPriority <= prio && prio <= MaxPriority, "sanity check");
5 // 使用Handle来绑定Java的Thread对象和C++的Thread
6
7 // 使用JNIHandles从jni线程中获取C++的线程对象,并将其放入一个新的Handle封装类的变量thread_oop中。
8 // 这个Handle主要用于共享该C++线程对象。
9
10 Handle thread_oop(Thread::current(),
11 JNIHandles::resolve_non_null(jni_thread));
12 assert(InstanceKlass::cast(thread_oop->klass())->is_linked(),
13 "must be initialized");
14 set_threadObj(thread_oop());
15 java_lang_Thread::set_thread(thread_oop(), this);
16
17 if (prio == NoPriority) {
18 prio = java_lang_Thread::priority(thread_oop());
19 assert(prio != NoPriority, "A valid priority should be present");
20 }
21
22 // 将线程的优先级设置成 Java中优先级 映射之后的 实际操作系统的线程优先级
23 Thread::set_priority(this, prio);
24
25 // 将线程加入线程队列中,并且该线程进行激活。
26 // 调用Threads::add时需要持有该线程的锁。
27 // 至关重要的一点是,在我们线程加入线程队列动作执行挖成之后,我们需要释放该线程的锁
28 // (也即JVM不会主动阻塞该线程),以防止GC无法回收该线程的内存。
29 Threads::add(this);
30}
这其中调用了thread.cpp中的方法Thread::set_priority对优先级进行设置。
1void Thread::set_priority(Thread* thread, ThreadPriority priority) {
2 debug_only(check_for_dangling_thread_pointer(thread);)
3 // Can return an error!
4 (void)os::set_priority(thread, priority);
5}
Thread::set_priority调用了os.cpp中os::set_priority进行实际的优先级测试。代码位于src\hotspot\share\runtime\os.cpp
。
1OSReturn os::set_priority(Thread* thread, ThreadPriority p) {
2 debug_only(Thread::check_for_dangling_thread_pointer(thread);)
3
4 if ((p >= MinPriority && p <= MaxPriority) ||
5 (p == CriticalPriority && thread->is_ConcurrentGC_thread())) {
6 int priority = java_to_os_priority[p];
7 return set_native_priority(thread, priority);
8 } else {
9 assert(false, "Should not happen");
10 return OS_ERR;
11 }
12}
方法设置中,有一个关键的优先级获取列表——java_to_os_priority。这个列表在不同的操作系统中,针对各个操作系统对于线程优先级的不同定义,进行了不同的实现。
1// linux中优先级的实现,参见src\hotspot\os\linux\os_linux.cpp
2int os::java_to_os_priority[CriticalPriority + 1] = {
3 19, // 0 Entry should never be used
4
5 4, // 1 MinPriority
6 3, // 2
7 2, // 3
8
9 1, // 4
10 0, // 5 NormPriority
11 -1, // 6
12
13 -2, // 7
14 -3, // 8
15 -4, // 9 NearMaxPriority
16
17 -5, // 10 MaxPriority
18
19 -5 // 11 CriticalPriority
20};
1// Windows中的实现,参见src\hotspot\os\windows\os_windows.cpp
2int os::java_to_os_priority[CriticalPriority + 1] = {
3 THREAD_PRIORITY_IDLE, // 0 Entry should never be used
4 THREAD_PRIORITY_LOWEST, // 1 MinPriority
5 THREAD_PRIORITY_LOWEST, // 2
6 THREAD_PRIORITY_BELOW_NORMAL, // 3
7 THREAD_PRIORITY_BELOW_NORMAL, // 4
8 THREAD_PRIORITY_NORMAL, // 5 NormPriority
9 THREAD_PRIORITY_NORMAL, // 6
10 THREAD_PRIORITY_ABOVE_NORMAL, // 7
11 THREAD_PRIORITY_ABOVE_NORMAL, // 8
12 THREAD_PRIORITY_HIGHEST, // 9 NearMaxPriority
13 THREAD_PRIORITY_HIGHEST, // 10 MaxPriority
14 THREAD_PRIORITY_HIGHEST // 11 CriticalPriority
15};
Thread::start方法
该方法的具体实现参见src/hotspot/share/runtime/thread.cpp
。
1void Thread::start(Thread* thread) {
2 // start和resume的区别在于,start方法的安全性是上下文或者Java代码中的Thread方法调用的
3 if (!DisableStartThread) {
4 if (thread->is_Java_thread()) {
5 // 初始化线程状态为RUNNABLE。如果先调用start_thread启动线程,那么线程的状态将由于无法预知而不能准确设置上。(有可能没有被成功获取到线程锁(MONITOR_WAIT)或者休眠(SLEEPING)或者其他状态)
6 java_lang_Thread::set_thread_status(((JavaThread*)thread)->threadObj(),
7 java_lang_Thread::RUNNABLE);
8 }
9 os::start_thread(thread);
10 }
11}
1void os::start_thread(Thread* thread) {
2 MutexLocker ml(thread->SR_lock(), Mutex::_no_safepoint_check_flag);
3 OSThread* osthread = thread->osthread();
4 osthread->set_state(RUNNABLE);
5 // 将操作系统的线程的状态设置成RUNNABLE
6 pd_start_thread(thread);
7}
同样以os_linux实现为例。
1void os::pd_start_thread(Thread* thread) {
2 OSThread * osthread = thread->osthread();
3 assert(osthread->get_state() != INITIALIZED, "just checking");
4 Monitor* sync_with_child = osthread->startThread_lock();
5 MutexLocker ml(sync_with_child, Mutex::_no_safepoint_check_flag);
6 // 尝试唤起该osthread
7 sync_with_child->notify();
8}
总结
写到这里,最后总结一下,通过源码阅读,我们其实已经不难发现在HotSpot虚机中,线程的创建其实是JVM将操作系统的线程创建做了一次二次封装。
整体主要的线程创建的流程为:
- 计算线程所需要的内存空间;
- 向操作系统申请创建一个线程;
- 对该线程设置优先级;
- 将线程加入线程队列;
- 将线程的状态设置成RUNNABLE并且让操作系统尝试唤醒。
时序图如下:
这里面有一个比较重要的注意事项,由于不同的操作系统对于线程的优先级的设置的范围不一样(参见上方os::java_to_os_priority)。
因此在代码开发过程中,不建议针对线程设置不同的优先级。
如果一定需要设置优先级,也建议尽可能地减少优先级的梯度,同时让这些优先级尽可能均匀的分散在java.lang.Thread#MIN_PRIORITY
和java.lang.Thread#MAX_PRIORITY
之间(通常对应的是1-10)。
或者翻阅源码或者相关操作系统手册,确定自己优先级设置能够正常映射到对应的优先级上,否则就可能出现无法预料的线程调度错乱问题。