本文出自:【InTheWorld的博客】 (欢迎留言、交流)
前言
之前研究OpenJDK的时候,并没有注意Java线程模型方面的东西。最近在学习一些Java并发方面的知识时,关于JVM线程实现原理的疑问又浮上心头。昨天晚上,突然想起以前研究OpenJDK的资料还在,于是开始在OpenJDK的源码里面研究Thread的实现。搞到凌晨三点多,差不多弄清楚了一个大概,这篇博文就主要把把相关的知识点记录下来。
1. Java中Thread
对于Java开发者来说,java.lang.Thread的确是再熟悉不过了。Java多线程程序都是通过它来实现功能的。有关Thread的使用方式,这里我不想赘述了,而是直奔Thread的实现原理。任何线程的实现都需要内核线程的支持,至少要有一个,不然程序无法执行。用户线程和内核线程的比例(M:N)体现了线程的不同实现方式。而对于高版本的Java(1.3及以后版本)来说,一个Java的Thread就对应一个操作系统的线程,当然少数平台上会有一些区别。
1:1的线程模型其实是非常简单的,带着这个结论,我们通过源码来看看这个1:1模型是如何实现的。首先还是看看java.lang.Thread的源码吧!
public class Thread implements Runnable { /* Make sure registerNatives is the first thingdoes. */ private static native void registerNatives(); static { registerNatives(); } public synchronized void start() { if (threadStatus != 0) throw new IllegalThreadStateException(); group.add(this); boolean started = false; try { start0(); started = true; } finally { try { if (!started) { group.threadStartFailed(this); } } catch (Throwable ignore) { /* do nothing. If start0 threw a Throwable then it will be passed up the call stack */ } } } private native void start0(); }
2. Thread的JNI接口
对于一个Java线程来说,start可能是最常用的方法了。通过源码我们可以看到,这个start0()才是真正实现线程启动的函数。然而很不幸,这个一个native函数,它是通过C/C++实现的。事实上,Thread中的大部分重要操作都是native函数完成的,所以,我们需要到OpenJdk里面找到这些函数的实现。所以看看Thread对应的JNI实现,详细代码如下:
/* openjdk\jdk\src\share\native\java\lang\Thread.c */ #include "jni.h" #include "jvm.h" #include "java_lang_Thread.h" #define THD "Ljava/lang/Thread;" #define OBJ "Ljava/lang/Object;" #define STE "Ljava/lang/StackTraceElement;" #define STR "Ljava/lang/String;" #define ARRAY_LENGTH(a) (sizeof(a)/sizeof(a[0])) 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}, }; #undef THD #undef OBJ #undef STE #undef STR JNIEXPORT void JNICALL Java_java_lang_Thread_registerNatives(JNIEnv *env, jclass cls) { (*env)->RegisterNatives(env, cls, methods, ARRAY_LENGTH(methods)); }
JNI的实现很简单,直接暴露的registerNatives()函数实现了多个native函数的注册。了解pthread接口的同学都应该知道,pthread的接口不同于Java Thread,pthread_create()的时候,新线程就启动了。而Java Thread则不同,Java Thread的启动需要调用start()方法,所以可以猜测Java线程对应的native线程其实是在start的时候调用的。补充一句,native线程的实现是平台相关的,这里我选择Linux平台进行分析,Windows虽然没有实现Posix thread接口,但实际上已经和Posix很接近了,所以理解了Linux平台实现,Windows下的实现也可以轻松理解。关于线程启动的start0()方法对应的函数代码如下:
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. { 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)); size_t sz = size > 0 ? (size_t) size : 0; native_thread = new JavaThread(&thread_entry, sz); 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()); } Thread::start(native_thread); JVM_END
第三行代码定义了一个JavaThread类型的native_thread指针,这个变量就是Java thread对应的native线程数据结构。对于JavaThread这个C++类的具体实现,稍后再分析。首先看看,JavaThread和Java的Thread是如何联系起来的?答案就是native_thread->prepare(jthread)这行代码。这个函数的源码如下,非常简单。
set_threadObj(thread_oop()); java_lang_Thread::set_thread(thread_oop(), this);
第一行是把Java的Thread信息记录到JavaThread对象中,第二行则是把JavaThread对象记录到Java的Thread对象中。从此,Java线程和C++线程就联系起来了。
3. C++的JavaThread
C++的JavaThread提供线程的实质性操作。JavaThread是Thread的子类,C++ Thread类还有其他一些子类,这里暂且按下不表。JavaThread是一个比较复杂的类,毕竟它实现了很多的功能。这里就简要介绍一下JavaThread的构造和启动,JavaThread的其中一个重要构造函数如下:
JavaThread::JavaThread(ThreadFunction entry_point, size_t stack_sz) : Thread() { 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; }
os::create_thread()实现了实际的线程创建的工作。create_thread()函数的主干代码如下:
bool os::create_thread(Thread* thread, ThreadType thr_type, size_t stack_size) { OSThread* osthread = new OSThread(NULL, NULL); if (osthread == NULL) { return false; } osthread->set_thread_type(thr_type); // Initial state is ALLOCATED but not INITIALIZED osthread->set_state(ALLOCATED); thread->set_osthread(osthread); // init thread attributes pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); ThreadState state; { bool lock = os::Linux::is_LinuxThreads() && !os::Linux::is_floating_stack(); if (lock) { os::Linux::createThread_lock()->lock_without_safepoint_check(); } pthread_t tid; int ret = pthread_create(&tid, &attr, (void* (*)(void*)) java_start, thread); pthread_attr_destroy(&attr); } return true; }
pthread_create(&tid, &attr, (void* ()(void)) java_start, thread)语句中的java_start是JavaThread的routing函数。这个函数中会等待一个信号量,在获得信号量后,它会调用JavaThread对应的java.lang.Thread对象的run()方法。而这个信号量是通过Thread::start(native_thread)这个函数发送的。
到这里JavaThread的构造和启动就基本了解各大概了。这篇《JVM原理与实现——Thread》就先到这里了。欢迎留言、交流、讨论!
看过树伟大神很多文章,很多技术点之前了解一些,看过之后加深了理解,确实很不错。建议树伟大神多写一些分布式架构的文章,毕竟单纯技术点的实践意义不太强,整合起来大家对技术点的定位及使用场景会有整体认识。
谢谢建议,我会好好考虑的!