JVM原理与实现——Thread

= 1364

本文出自:【InTheWorld的博客】 (欢迎留言、交流)

Jaa

前言

之前研究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 thing <clinit> does. */
    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》就先到这里了。欢迎留言、交流、讨论!眨眼

已有2条评论 发表评论

  1. 长颈鹿的天都是晴天 /

    看过树伟大神很多文章,很多技术点之前了解一些,看过之后加深了理解,确实很不错。建议树伟大神多写一些分布式架构的文章,毕竟单纯技术点的实践意义不太强,整合起来大家对技术点的定位及使用场景会有整体认识。

    1. lshw4320814 / 本文作者

      谢谢建议,我会好好考虑的!

发表评论