java线程理解以及openjdk中的实现

>>强大,10k+点赞的 SpringBoot 后台管理系统竟然出了详细教程!

看了《深入理解java虚拟机》java与线程一章提到了linux提供的线程模型是一对一的。我也写过一段linux c,当时开辟多线程也就是调用了pthread_create的库函数。

linux c 线程函数

int pthread_create(pthread_t *tid,const pthread_attr_t *attr,
(void*
)(*start_rtn)(void*),void *arg)
;

tid就是线程标识

attr是线程属性

start_rtn是函数指针,就是线程运行的函数

arg是函数的参数

这个函数的运用比较简单,就是把你线程要执行的函数的指针传递给第三个参数,把函数的参数用第四个参数传入。

我的问题也就来了,java的线程执行的内容是写在run方法的,那么c的线程是如何调用java的方法的。最开始我以为是jni做的,就看了看openjdk的源码来验证。

openjdk的实现

java线程是通过start的方法启动执行的,主要内容在native方法start0中。

openjdk的写jni一般是一一对应的,Thread.java对应的就是Thread.c。

static JNINativeMethod methods[] = {
   {"start0",           "()V",        (void *)&JVM_StartThread},
   {"stop0",            "(" OBJ ")V", (void *)&JVM_StopThread},
   {"isAlive",          "()Z",        (void *)&JVM_IsThreadAlive},
   {"suspend0",         "()V",        (void *)&JVM_SuspendThread},
   {"resume0",          "()V",        (void *)&JVM_ResumeThread},
   {"setPriority0",     "(I)V",       (void *)&JVM_SetThreadPriority},
   {"yield",            "()V",        (void *)&JVM_Yield},
   {"sleep",            "(J)V",       (void *)&JVM_Sleep},
   {"currentThread",    "()" THD,     (void *)&JVM_CurrentThread},
   {"countStackFrames", "()I",        (void *)&JVM_CountStackFrames},
   {"interrupt0",       "()V",        (void *)&JVM_Interrupt},
   {"isInterrupted",    "(Z)Z",       (void *)&JVM_IsInterrupted},
   {"holdsLock",        "(" OBJ ")Z", (void *)&JVM_HoldsLock},
   {"getThreads",        "()[" THD,   (void *)&JVM_GetAllThreads},
   {"dumpThreads",      "([" THD ")[[" STE, (void *)&JVM_DumpThreads},
   {"setNativeName",    "(" STR ")V", (void *)&JVM_SetNativeThreadName},
};

start0其实就是JVM_StartThread。此时通过search and replace工具查找JVM_StartThread关键字,在jvm.h中找到了声明,jvm.cpp中有实现。

JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))
 JVMWrapper("JVM_StartThread");
 JavaThread *native_thread = NULL;
 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);
   if (java_lang_Thread::thread(JNIHandles::resolve_non_null(jthread)) != NULL) {
     throw_illegal_thread_state = true;
   } else {
     jlong 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 signed, but the constructor takes
     // size_t (an unsigned type), so avoid passing negative values which would
     // result in really large stacks.
     size_t sz = size > 0 ? (size_t) size : 0;
     native_thread = new JavaThread(&thread_entry, sz);
 ……
 ……

大概浏览上下文,native_thread的构造应该是在JavaThread的构造中完成的,此处代码就展示到构造JavaThread处。

JavaThread::JavaThread(ThreadFunction entry_point, size_t stack_sz) :
 Thread()
#if INCLUDE_ALL_GCS
 , _satb_mark_queue(&_satb_mark_queue_set),
 _dirty_card_queue(&_dirty_card_queue_set)
#endif // INCLUDE_ALL_GCS
{
 if (TraceThreadEvents) {
   tty->print_cr("creating thread %p", this);
 }
 initialize();
 _jni_attach_state = _not_attaching_via_jni;
 set_entry_point(entry_point);
 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;
}

在构造函数中做了几件事,设置entry_point,设置stack_size,create_thread。entry_point在后面讲,这里需要知道此处有这么一个点。我们继续看线程的实现。线程的实现基本是基于系统库函数的(c++11才有的统一线程库)。具体实现在os_linux.cpp中(这里我们只说linux的,其他系统的实现就不在这里了)。

bool os::create_thread(Thread* thread, ThreadType thr_type, size_t stack_size) {
   ……
   pthread_t tid;
   int ret = pthread_create(&tid, &attr, (void* (*)(void*)) java_start, thread);
    ……

这里终于看到了系统库函数的调用。线程执行的方法是java_start,参数是thread。

// Thread start routine for all newly created threads
static void *java_start(Thread *thread) {
 // 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.
 static int counter = 0;
 int pid = os::current_process_id();
 alloca(((pid ^ counter++) & 7) * 128);
 ThreadLocalStorage::set_thread(thread);
 OSThread* osthread = thread->osthread();
 Monitor* sync = osthread->startThread_lock();
 // non floating stack LinuxThreads needs extra check, see above
 if (!_thread_safety_check(thread)) {
   // notify parent thread
   MutexLockerEx ml(sync, Mutex::_no_safepoint_check_flag);
   osthread->set_state(ZOMBIE);
   sync->notify_all();
   return NULL;
 }
 // thread_id is kernel thread id (similar to Solaris LWP id)
 osthread->set_thread_id(os::Linux::gettid());
 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 thread
 os::Linux::hotspot_sigmask(thread);
 // initialize floating point control register
 os::Linux::init_thread_fpu_state();
 // handshaking with parent thread
 {
   MutexLockerEx ml(sync, Mutex::_no_safepoint_check_flag);
   // notify parent thread
   osthread->set_state(INITIALIZED);
   sync->notify_all();
   // wait until os::start_thread()
   while (osthread->get_state() == INITIALIZED) {
     sync->wait(Mutex::_no_safepoint_check_flag);
   }
 }
 // call one more level start routine
 thread->run();
 return 0;
}

这个方法可以直接跳到最后一行,他执行的是传递过来对象的run方法,大家还记得前面构造的JavaThread对象吧。

// The first routine called by a new Java thread
void JavaThread::run() {
 // initialize thread-local alloc buffer related fields
 this->initialize_tlab();
 // used to test validitity of stack trace backs
 this->record_base_of_stack_pointer();
 // Record real stack base and size.
 this->record_stack_base_and_size();
 // Initialize thread local storage; set before calling MutexLocker
 this->initialize_thread_local_storage();
 this->create_stack_guard_pages();
 this->cache_global_variables();
 ThreadStateTransition::transition_and_fence(this, _thread_new, _thread_in_vm);
 assert(JavaThread::current() == this, "sanity check");
 assert(!Thread::current()->owns_locks(), "sanity check");
 DTRACE_THREAD_PROBE(start, this);
 // This operation might block. We call that after all safepoint checks for a new thread has
 // been completed.
 this->set_active_handles(JNIHandleBlock::allocate_block());
 if (JvmtiExport::should_post_thread_life()) {
   JvmtiExport::post_thread_start(this);
 }
 EventThreadStart event;
 if (event.should_commit()) {
    event.set_javalangthread(java_lang_Thread::thread_id(this->threadObj()));
    event.commit();
 }
 thread_main_inner();
}

这里的代码需要大概过一下,我们看到方法的开头初始化很多信息,例如TLAB,TLS 等。真正执行的代码在thread_main_inner中。

void JavaThread::thread_main_inner() {
 assert(JavaThread::current() == this, "sanity check");
 assert(this->threadObj() != NULL, "just checking");
 // Execute thread entry point unless this thread has a pending exception
 // or has been stopped before starting.
 // Note: Due to JVM_StopThread we can have pending exceptions already!
 if (!this->has_pending_exception() &&
     !java_lang_Thread::is_stillborn(this->threadObj())) {
   {
     ResourceMark rm(this);
     this->set_native_thread_name(this->get_thread_name());
   }
   HandleMark hm(this);
   this->entry_point()(this, this);
 }
 DTRACE_THREAD_PROBE(stop, this);
 this->exit(false);
 delete this;
}

这个方法看到最后都发现没有执行run方法吧,这也是我第一次跟踪代码走完很奇特的地方,完全没说run方法的事情。  this->entry_point()(this, this);其实就是调用run方法的。上面我特意说记着entry_point这个东西。这个在构造JavaThread传入的,是一个名字叫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);
}

里面有JavaCalls的过程,传入的就是 vmSymbols::run_method_name(),就是run方法。具体call_virtual怎么调用的。请参考https://www.zhihu.com/question/64677339/answer/223307974我也是参考了这里回答才明白的。

总结

jvm在linux中线程的实现就是调用的pthread库,但是线程执行的内容,却是解释执行的(就是上面说的call_virtual的过程),并不是jni的反调。我的理解就是每个c的线程都在解释执行java的run方法,从而达到了java的多线程效果。


原文始发于微信公众号(一次编译多处运行):java线程理解以及openjdk中的实现