【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源码参见如下:

  1. HotSpot的开源项目位于:OpenJDK/JDK
  2. 源代码仓库为:OpenJdk
  3. 仓库中源代码对应JDK 15的TAG为jdk-15-ga
  4. 我的所有中文注释均上传在我个人fork出来的仓库中,参见YunyaoLong/jdk,提交分支为jdk-15-ga-ch-cn

太长不看

线程的创建其实是JVM将操作系统的线程创建做了一次二次封装。

整体主要的线程创建的流程为:

  1. 计算线程所需要的内存空间;
  2. 向操作系统申请创建一个线程;
  3. 对该线程设置优先级;
  4. 将线程加入线程队列;
  5. 将线程的状态设置成RUNNABLE并且让操作系统尝试唤醒。

这由于不同的操作系统对于线程的优先级的设置的范围不一样,因此在代码开发过程中,不建议针对线程设置不同的优先级

如果一定需要设置优先级,也建议尽可能地减少优先级的梯度,同时让这些优先级尽可能均匀的分散在java.lang.Thread#MIN_PRIORITYjava.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#start0native方法具体实现的。

(这里其实可以发现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

其中有几个比较重要的函数:

  1. 线程对象创建函数new JavaThread(&thread_entry, sz)
  2. OS线程查看入口native_thread->osthread()
  3. 线程状态初始化函数native_thread->prepare(jthread)
  4. 线程启动函数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.cppos_windows.cppos_posix.cppos_aix.cppos_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将操作系统的线程创建做了一次二次封装。

整体主要的线程创建的流程为:

  1. 计算线程所需要的内存空间;
  2. 向操作系统申请创建一个线程;
  3. 对该线程设置优先级;
  4. 将线程加入线程队列;
  5. 将线程的状态设置成RUNNABLE并且让操作系统尝试唤醒。

时序图如下:

线程调度时序图

这里面有一个比较重要的注意事项,由于不同的操作系统对于线程的优先级的设置的范围不一样(参见上方os::java_to_os_priority)。

因此在代码开发过程中,不建议针对线程设置不同的优先级

如果一定需要设置优先级,也建议尽可能地减少优先级的梯度,同时让这些优先级尽可能均匀的分散在java.lang.Thread#MIN_PRIORITYjava.lang.Thread#MAX_PRIORITY之间(通常对应的是1-10)。

或者翻阅源码或者相关操作系统手册,确定自己优先级设置能够正常映射到对应的优先级上,否则就可能出现无法预料的线程调度错乱问题。