从Java到C++,以JVM的角度看Java线程的创建与运行

写在前面

写作时间:2017.5
本文JDK版本:JDK 1.8
本文简述:从Java的新建一个线程开始,溯源到Thread类的源码,然后再从Thread类的源码跳到虚拟机层的C+ +源码。虚拟机层是平台相关的,但我没有对每个平台的实现机制都探索一遍,于是只选取Linux部分的实现源码进行探究。而C++的代码在逻辑上又分为几层,逐层深挖,一直到C++新建的线程调用Java线程的run()方法结束。(本文每处都贴有源码地址,可作参考)
本文持续更新地址:http://www.jianshu.com/p/3ce1b5e5a55e

附:
本文相当长,可以先看结尾的总结再回头看正文,如果你只想理解个大概,也可以只看java部分以及结尾总结。
可能我的文笔以及实力都有所欠缺,想要完全理解的话可能需要多看几遍以及花点时间,请诸君包涵。(但做学问需要的不正是沉得住气,耐得住寂寞吗)
然而,代码细节实在是太多了,硬吃实在不是个办法,所以我的建议是,最开始可以先不看代码,只看每个小节后面的总结和代码中的中文注释,甚至是先看流程终结,再看小节的总结,最后有兴趣才开始研究源码。
切忌“只见树木,不见森林”

线程的创建

故事的开始,是这样的一段的代码:

Thread thread = new Thread(new Runnable() {
    @Override
    public void run() {             
        System.out.println(Thread.currentThread().getName());
    }
});
        
thread.start();

然后我们的目标就是,在C++源码中找到这个run()方法是在哪里执行的。
首先进入的是它的构造方法:

    public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }

经过一系列的调用,最终跳到这个方法,(这里的代码有点长,就不给出完整代码,完整的源码在这里:源码传送门

/**
     * Initializes a Thread.
     *
     * @param g 线程组
     * @param target 携带run方法的Runnable对象
     * @param name 线程名
     * @param stackSize  新线程的需要的stack size,若此值为0则表明此属性忽略
     * @param acc 将要被继承的AccessControlContext,若此值为null,则此属性默认设置为
     *            AccessController.getContext()
     * @param inheritThreadLocals 若此值为true,从构造此线程的线程中继承thread-locals的初始值
     */
    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }

        this.name = name;

        Thread parent = currentThread();
        SecurityManager security = System.getSecurityManager();
        if (g == null) {
            /* Determine if it's an applet or not */

            /* If there is a security manager, ask the security manager
               what to do. */
            if (security != null) {
                g = security.getThreadGroup();
            }

            /* If the security doesn't have a strong opinion of the matter
               use the parent thread group. */
            if (g == null) {
                g = parent.getThreadGroup();
            }
        }


        g.addUnstarted();

        this.group = g;
        this.daemon = parent.isDaemon();
        this.priority = parent.getPriority();
       
        this.target = target;
        setPriority(priority);
        
        this.stackSize = stackSize;

        /* Set thread ID */
        tid = nextThreadID();                  
    }

稍微尝试着总结一下:

  1. 设置线程名(即设置name属性),一般情况下,这个name的格式是“Thread-X”,X是一个数字,代表用这个线程类初始化的第X个实例
  2. 设置线程的ThreadGroup,这个ThreadGroup要么从应用的SecurityManager处拿到,要么就直接继承父线程的ThreadGroup。(设置完成以后还会有一个验证)
  3. 在真正设置此线程的ThreadGroup之前,需要为这个ThreadGroup的“还未开始的线程数目”(nUnstartedThreads)加一
  4. 设置此线程是否为守护线程(即设置daemon属性),若父线程为守护线程,那么此线程也是守护线程
  5. 设置此线程的优先级(即priority属性)为父线程的优先级(顺便补充一句,子线程的优先级是不能大于父线程的)
  6. 设置此线程的上下文类加载器
  7. 设置这个Thread的target,这个target就是携带了我们写好的run()方法的Runnable对象
  8. 设置stackSize
  9. 设置线程ID(tid属性)

关于run方法的一个补充

或许有人会说,我创建线程的时候,是使用传递一个Runnable对象来进行创建对象,并没有重写原来的run方法,那么到了这里,还调用run方法的话,不就出问题了。
其实,这个问题我们看一下Thread.java的run()方法就好了:

@Override
public void run(){
    if(target != null){
        target.run();
    }
}

target是什么,target就是我们在初始化Thread时传递进去的Runnable对象,那么这几行代码可以解释我们的几个疑问:

  1. 如果我们是通过传递Runnable对象来初始化Thread实例的话,那么就是将实例的run方法“替换”为Runnable对象的run()方法(逻辑上的替换)
  2. 如果我们的Thread继承类重写了run()方法,也就是说原本的“替换”逻辑被覆盖了,这时我们再通过传递Runnable对象来初始化Thread实例的话,这个实例的run不会再被替换,也就是这个实例仅会执行我们重写的run方法。

线程的开始

我们都知道,开始一个线程就是调用这个线程的start方法:

    /**
     * 调用此方法将会使得thread开始运行,JVM会在这个线程中调用run方法。
     * <p>
     * 调用结果是,两个线程同时运行:调用此线程的线程,以及此线程
     * <p>
     * 无论在什么情况下,要求同一条线程开始两次都是不合法,即便是此线程已经结束在要求开始也是不合法的。
     *
     * @exception  IllegalThreadStateException  若此线程曾经开始(start)过
     * @see        #run()
     * @see        #stop()
     */
    public synchronized void start() {
        /**
         * 
         * 对于main方法线程(即运行main方法的线程),或者是由虚拟机创建的“system”组的
         * 线程,都不需要调用此方法来启动。
         * 任何在这个方法中添加的新功能,以后都有可能添加到虚拟机上。
         * 翻译的不是很满意,原文留在这里:
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         * 
         * 若线程的threadStatus为0,则表明这个状态是NEW状态
         */
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        /* 
         * 通知线程组:此线程已经就绪,可以开始运行了
         * 所以,这条线程可以添加到线程组的线程list中,以及线程组
         * 未开始线程的数目(he group's unstarted count)可以减一了。
         * 
         * */
        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 */
            }
        }
    }

挺简短的一段代码,我们可以总结出以下步骤:

第一步,非常重要,就是先检查线程的状态,如果线程的状态不是NEW状态的话,那么就会抛出一个异常(关于Java线程的状态,可以看这篇文章:Java 线程的几种状态
第二步,将这个Thread添加到之前引用的ThreadGroup中
第三步,调用native start0()

关于Thread类中的native方法

在Thread.java里面,有一个registerNatives()的native方法,并且在Threa.java的第一个static块中就调用了这个方法,保证这个方法在类加载中是第一个被调用的方法。这个native方法的作用是为其他native方法注册到JVM中(可见:JVM查找java native方法的规则)。
通过这个方法,我们可以找到Thrad.java中native方法在JVM中对应方法:
Thread.c$Java_java_lang_Thread_registerNatives

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},
};
JNIEXPORT void JNICALL
Java_java_lang_Thread_registerNatives(JNIEnv *env, jclass cls)
{
    (*env)->RegisterNatives(env, cls, methods, ARRAY_LENGTH(methods));
}

0、在进入C之前的准备

我们将会需要使用到这几个概念:

java.lang.Thread: 这个是Java语言里的线程类,由这个Java类创建的instance都会 1:1 映射到一个操作系统的osthread;
JavaThread: JVM中C++定义的类,一个JavaThread的instance代表了在JVM中的java.lang.Thread的instance, 它维护了线程的状态,并且维护一个指针指向java.lang.Thread创建的对象(oop)。它同时还维护了一个指针指向对应的OSThread,来获取底层操作系统创建的osthread的状态
OSThread: JVM中C++定义的类,代表了JVM中对底层操作系统的osthread的抽象,它维护着实际操作系统创建的线程句柄handle,可以获取底层osthread的状态
VMThread: JVM中C++定义的类,这个类和用户创建的线程无关,是JVM本身用来进行虚拟机操作的线程,比如GC

(引自:聊聊JVM(五)从JVM角度理解线程

补充:需要注意的是osthread和OSThread的区别,简单的说,osthread是操作系统级别的线程,也就是真正意义上的线程,它的具体实现由操作系统完成;
而OSThread则是关于osthread的抽象,通过操作系统暴露出来的接口(句柄)对osthread进行操作,在实际的运行中,OSThread会根据所运行的操作系统(平台)来创建,也就是说,如果字节码运行的平台是Linux的话,那么创建出来的OSThread就是Linux的OSThread;如果是Windows的话,那么创建出来的就是Windows的OSThread

0.5、流程概述

为了避免“只见树木,不见森林”,特意将流程总结提前到开始
撇开源码,整个过程大概是:

  1. 在Java中,使用java.lang.Thread的构造方法来构建一个java.lang.Thread对象,此时只是对这个对象的部分字段(例如线程名,优先级等)进行初始化;
  2. 调用java.lang.Thread对象的start()方法,开始此线程。此时,在start()方法内部,调用start0() 本地方法来开始此线程;
  3. start0()在VM中对应的是JVM_StartThread,也就是,在VM中,实际运行的是JVM_StartThread方法(宏),在这个方法中,创建了一个JavaThread对象;
  4. 在JavaThread对象的创建过程中,会根据运行平台创建一个对应的OSThread对象,且JavaThread保持这个OSThread对象的引用;
  5. 在OSThread对象的创建过程中,创建一个平台相关的底层级线程,如果这个底层级线程失败,那么就抛出异常;
  6. 在正常情况下,这个底层级的线程开始运行,并执行java.lang.Thread对象的run方法;
  7. 当java.lang.Thread生成的Object的run()方法执行完毕返回后,或者抛出异常终止后,终止native thread;
  8. 最后就是释放相关的资源(包括内存、锁等)

在上述过程,穿插着各种的判断检测,其中很大一部分都是关于各种层次下的线程的状态的检测,在JVM中,无论哪种层次的线程,都只允许执行一次。

1、从Java进入C:JVM_StartThread

首先JVM会进入jvm.cpp的jvm.cpp$JVM_StartThread 方法(确切地说,这个JVM_StartThread是一个宏):
关于native的函数参数

第一个参数:JNIEnv* 是定义任意native函数的第一个参数(包括调用JNI的RegisterNatives函数注册的函数),指向JVM函数表的指针,函数表中的每一个入口指向一个JNI函数,每个函数用于访问JVM中特定的数据结构。
第二个参数:调用java中native方法的实例或Class对象,如果这个native方法是实例方法,则该参数是jobject,如果是静态方法,则是jclass

接下来我们来看下这个JVM_StartThread方法:

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.
  {

    //在我们操作的时候,确保C++线程和OSThread不会被提前释放
    //(译者附:)从上面的一个左花括号开始,一直到下一个匹配的右花括号为止,都上了关于Threads_lock的互斥锁
    //可以理解为Java的sync块
    MutexLocker mu(Threads_lock);
    

    // 安全原因,先进行一次检测,个人猜测是,判断这个线程是否已被启动过,若是已启动过,抛线程状态异常
    if (java_lang_Thread::thread(JNIHandles::resolve_non_null(jthread)) != NULL) {
      throw_illegal_thread_state = true;
    } else {
      // We could also check the stillborn flag to see if this thread was already stopped, but
      // for historical reasons we let the thread detect that itself when it starts running

      jlong size =
             java_lang_Thread::stackSize(JNIHandles::resolve_non_null(jthread));
             
      // 为C++ thread结构体分配内存,以及创建一个本地线程。这个从Java取过来的stack size是有一个符号数,
      // 但构造器需要的size_t是一个无符号数,所以在转换的时候需要避免因为size是负数出现的问题。
      size_t sz = size > 0 ? (size_t) size : 0;
      native_thread = new JavaThread(&thread_entry, sz);
      // 创建一个JavaThrea对象(创建过程见第二节),其中第二个参数thread_entry是一个方法指针,详见本节下方补充
      // ***************** 注意这个thread_entry  ************************
      // ***************** 注意这个thread_entry  ************************
      // ***************** 注意这个thread_entry  ************************

      // 仍做一次安全检测,因为osthread不一定能创建成功(详见下文第三节的osthread创建)
      if (native_thread->osthread() != NULL) {
        // 注意:当前线程在下面的“prepare”没有被使用到。
        native_thread->prepare(jthread);
      }
    }
  }

  if (throw_illegal_thread_state) {
    THROW(vmSymbols::java_lang_IllegalThreadStateException());
  }

  assert(native_thread != NULL, "Starting null thread?");

    //事实上,如果native_thread的osthread为NULL,就可以宣告整个线程创建过程失败
    //这时应该整理内存,并通知java层线程创建失败
  if (native_thread->osthread() == NULL) {
    // No one should hold a reference to the 'native_thread'.
    delete native_thread;
    if (JvmtiExport::should_post_resource_exhausted()) {
      JvmtiExport::post_resource_exhausted(
        JVMTI_RESOURCE_EXHAUSTED_OOM_ERROR | JVMTI_RESOURCE_EXHAUSTED_THREADS,
        "unable to create new native thread");
    }
    THROW_MSG(vmSymbols::java_lang_OutOfMemoryError(),
              "unable to create new native thread");
  }

  Thread::start(native_thread);

JVM_END

关于thread_entry方法的补充:注意,这个方法非常重要,后面还会出现,所以我们留到后面再解释【第6节】。


然后我们来看一下JVM在这个宏里面做了什么:

  1. 申请一个锁
  2. 创建一个JavaThread对象【见第二节】
  3. 如果有异常情况,抛出异常
  4. 对第二步的JavaThread类,做一个准备操作:thread.cpp$JavaThread::prepare
  5. 调用Thread::start方法,让一条线程开始运行

2、JavaThread对象的创建

本节是接上面小节中间,关于JavaThread对象创建的讲述的。
JavaThread是Java层的线程与平台层线程中间的一个过渡:
一个java.lang.Thread对象,在其start方法中,会创建一个JavaThread对象,由于java.lang.Thread对象的start方法只允许调用一次,所以java.lang.Thread对象与JavaThread对象是一一对应的
而在JavaThread对象的创建过程,会创建一个OSThread对象,并且JavaThread对象会持有一个指向该OSThread对象的指针(至于什么是OSThread,大概是平台相关的系统层线程,更详细见第三节)
thread.cpp$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);
  // Create the native thread itself.
  // %note runtime_23
  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);
  // 在这里,由于内存不足的原因,_osthread有可能为NULL、
  // 而我们应当抛出OutOfMenoryError,然而现在我们还不能抛出Error
  // 因为此方法的调用者有可能扔持有着所有的锁,而我们必须释放所有的锁才能抛出后异常
  // (抛出异常的工作包括:创建并初始化一个exception对象,而初始化exception对象则必须要通过JavaCall离开VM,
  // 然后所有的锁都必须要被释放)
  //
  // 此时这条线程扔处在挂起的状态,而线程必须由创建者显式地启动!
  // 此外,线程还必须显式地通过Threads:add方法被添加到Threads list 中。
  // 上面的工作(显式启动,显式添加)之所以到达这里还没完成,是因为线程还没有被完成地初始化(可参见JVM_Start)
}

完成的操作有:

  1. 调用initialize方法对JavaThread进行初始化,此方法仅是对结构体的属性做赋值;
  2. 为这个JavaThread设置一个入口方法set_entry_point
  3. 根据平台调用os::create_thread()方法来创建一个OSThread对象【见第三节】

3、根据平台创建OSThread

在这一步中,OSThread的创建是根据平台来选择,这里我是使用Linux部分的代码来进行研究的
OSThread是一个平台相关线程,OSThread由JavaThread对象创建并进行管理。
在OSThread创建的过程中,会通过pthread方法来创建一个真正意义上的底层级线程,

os_linux.cpp$os::create_thread
这个方法传入三个参数:
第一个是我们之前在JavaThread构造方法中创建的JavaThread对象;
第二个是一个ThreadType对象,只有两种可能,一种是os::compiler_thread,另一种是os::java_thread
第三个是,线程栈的大小

bool os::create_thread(Thread* thread, ThreadType thr_type, size_t stack_size) {
  assert(thread->osthread() == NULL, "caller responsible");

  // 为OSThread对象分配内存
  OSThread* osthread = new OSThread(NULL, NULL);
  //(注:)关于这个构造方法,并没有做出特殊的操作,仅是初始化一些属性,详见下方补充
  
  if (osthread == NULL) {
    return false;
  }

  // 设置正确的状态
  osthread->set_thread_type(thr_type);

  // 初始状态应该是ALLOCATED 而不是INITIALIZED
  osthread->set_state(ALLOCATED);

    //使得JavaThread的osthread指针指向新建的osthread
  thread->set_osthread(osthread);

  // 初始化线程
  pthread_attr_t attr;
  pthread_attr_init(&attr);
  pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);

  // stack size
  if (os::Linux::supports_variable_stack_size()) {
    // 如果调用者没有指定stack size,那么就需要对这个stack size进行计算
    if (stack_size == 0) {
      stack_size = os::Linux::default_stack_size(thr_type);

      switch (thr_type) {
      case os::java_thread:
        // Java threads use ThreadStackSize which default value can be
        // changed with the flag -Xss
        assert (JavaThread::stack_size_at_create() > 0, "this should be set");
        stack_size = JavaThread::stack_size_at_create();
        break;
      case os::compiler_thread:
        if (CompilerThreadStackSize > 0) {
          stack_size = (size_t)(CompilerThreadStackSize * K);
          break;
        } // else fall through:
          // use VMThreadStackSize if CompilerThreadStackSize is not defined
      case os::vm_thread:
      case os::pgc_thread:
      case os::cgc_thread:
      case os::watcher_thread:
        if (VMThreadStackSize > 0) stack_size = (size_t)(VMThreadStackSize * K);
        break;
      }
    }

    stack_size = MAX2(stack_size, os::Linux::min_stack_allowed);
    pthread_attr_setstacksize(&attr, stack_size);
  } else {
    // let pthread_create() pick the default value.
  }

  // glibc guard page
  pthread_attr_setguardsize(&attr, os::Linux::default_guard_size(thr_type));

  ThreadState state;

  {
    // Serialize thread creation if we are running with fixed stack LinuxThreads
    // (好吧,我也不是很懂这句注释的意思,
    // 尝试着直译一下:如果我们正在运行的是固定stack的LinuxThreads,那么就序列化线程的创建过程)
    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);
    
    //(注:)这个方法是c在Linux下创建并执行一个平台层的线程的方法
    // 新建好的线程,应该执行的是java_start方法(即第二个参数)
    //如果返回值是0,表示线程创建成功,否则表示线程创建失败,关于这个方法的详细介绍,见下方补充
    
    pthread_attr_destroy(&attr);


    // 下面是平台级线程创建失败的一些处理
    // 如果osthread创建失败,就把JAVAThread的osthread设置为NULL
    if (ret != 0) {
      if (PrintMiscellaneous && (Verbose || WizardMode)) {
        perror("pthread_create()");
      }
      // Need to clean up stuff we've allocated so far
      thread->set_osthread(NULL);
      delete osthread;
      if (lock) os::Linux::createThread_lock()->unlock();
      return false;
    }

    // Store pthread info into the OSThread
    // 这个tid是刚才新建的底层级线程的一个标识符,我们需要通过这个标识符来管理底层级线程
    osthread->set_pthread_id(tid);

    // 在这里停顿!直到子线程的初始化完成,或者子线程被放弃(考虑到线程中还有线程的情况)
    {
      Monitor* sync_with_child = osthread->startThread_lock();
      MutexLockerEx ml(sync_with_child, Mutex::_no_safepoint_check_flag);
      while ((state = osthread->get_state()) == ALLOCATED) {
        sync_with_child->wait(Mutex::_no_safepoint_check_flag);
      }
    }

    if (lock) {
      os::Linux::createThread_lock()->unlock();
    }
  }

  // Aborted due to thread limit being reached
  if (state == ZOMBIE) {
      thread->set_osthread(NULL);
      delete osthread;
      return false;
  }
  
  

  // The thread is returned suspended (in state INITIALIZED),
  // and is started higher up in the call chain
  assert(state == INITIALIZED, "race condition");
  return true;
}

补充OSThread::OSThread:

OSThread::OSThread(OSThreadStartFunc start_proc, void* start_parm) {
  pd_initialize();
    //这个方法更是没有什么好讲的,只是将值初始化为初始值(及0或NULL)
    //要是要看的话,这个方法在os/linux/vm/osThread_linux.cpp
  set_start_proc(start_proc);
  set_start_parm(start_parm);
  set_interrupted(false);
}

补充创建系统级线程的pthread_create()方法

这个方法很长,做了以下的几件事:
1、 创建一个osthread对象,并初始化这个对象,这个初始化包括:设置thread_type,设置线程状态
2、让我们传进来的JavaThread对象保存这个osthread对象的引用
3、中间还有一些设置线程栈数量,获取锁之类的操作
4、864行,int ret = pthread_create(&tid, &attr, (void* ()(void)) java_start, thread);这个方法是C++创建线程的库方法,通过调用这个方法,会创建一个C++ 线程并使线程进入就绪状态,即可以开始运行
5、这个线程将执行java_start这个方法【见第四节】
6、开始收尾,清理一下内存

4、java_start方法

这个方法是OSThread创建的底层级线程中将要执行的方法,它的描述是:为所有新建的线程启动例程

os_linux.cpp$java_start

// 线程为所有新创建的线程启动例程。
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)
  {
    MutexLockerEx ml(sync, Mutex::_no_safepoint_check_flag);

    // 通知父线程
    osthread->set_state(INITIALIZED);
    sync->notify_all();

    // wait until os::start_thread()
    while (osthread->get_state() == INITIALIZED) {
      sync->wait(Mutex::_no_safepoint_check_flag);
    }
  }

  // 调用一个更高级别的开始例程(见第五节)
  thread->run();

  return 0;
}

这个方法做的事:
1、拿到这个线程的线程id
2、分配内存?
3、设置ThreadLocal
4、线程安全检测
5、中间还有一些东西,不想关心
6、同样地,再次设置这个thread的osthrad的状态
7、调用Thread的run方法,但是这个run方法是个虚方法,实际上调用的是JavaThread的run方法【见第五节】

5、JavaThread::run()

JavThread::run():

// 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();

    //线程基本初始化完成,在虚拟机中可以当作是safepoint代码来处理了
    //于是将线程的状态从 _thread_new切换到_thread_in_vm
  // Thread is now sufficient initialized to be handled by the safepoint code as being
  // in the VM. Change thread state from _thread_new to _thread_in_vm
  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.
  //这个操作有可能会被阻塞,我们需要要在所有safepoint检查完这个新线程以后才能调用此方法
  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();
  }

  // We call another function to do the rest so we are sure that the stack addresses used
  // from there will be lower than the stack base just computed
  thread_main_inner();

  // 注意,一旦运行到这里,线程就不再合法(valid)了
}

这个方法完成的事有:
1、初始化相关字段
2、将线程的状态从 _thread_new切换到_thread_in_vm
3、调用thread_main_inner()方法【见第六节】
(这里说的safepoint应该就是GC中要用到的safepoint吧,但是all safepoint checks for a new thread是什么意思,safepoint还能检查线程..?暂且搁置)

6、JavaThread::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;
}

然后又回到了之前的我们设置的entry_point():(可回看第一节)
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(),`
                          THREAD);
}

注意这个vmSymbols::run_method_name(),非常关键!

这个方法是在vmSymbolHandles中用宏来定义的,而这个vmSymbolHandles的描述是:

// The class vmSymbols is a name space for fast lookup of
// symbols commonly used in the VM.
//
// Sample usage:
//
//   Symbol* obj       = vmSymbols::java_lang_Object();

大意翻译为:vmSymbols是一个VM中使用的,用于常用符号的快速查找的命名空间

于是,我们在这个命名空间找到了这样的一行代码:

 template(run_method_name,                           "run")

也就是说,这里调用的是java.lang.Thread对象的run()方法!

总结

撇开源码,整个过程大概是:

  1. 在Java中,使用java.lang.Thread的构造方法来构建一个java.lang.Thread对象,此时只是对这个对象的部分字段(例如线程名,优先级等)进行初始化;
  2. 调用java.lang.Thread对象的start()方法,开始此线程。此时,在start()方法内部,调用start0() 本地方法来开始此线程;
  3. start0()在VM中对应的是JVM_StartThread,也就是,在VM中,实际运行的是JVM_StartThread方法(宏),在这个方法中,创建了一个JavaThread对象;
  4. 在JavaThread对象的创建过程中,会根据运行平台创建一个对应的OSThread对象,且JavaThread保持这个OSThread对象的引用;
  5. 在OSThread对象的创建过程中,创建一个平台相关的底层级线程,如果这个底层级线程失败,那么就抛出异常;
  6. 在正常情况下,这个底层级的线程开始运行,并执行java.lang.Thread对象的run方法;
  7. 当java.lang.Thread生成的Object的run()方法执行完毕返回后,或者抛出异常终止后,终止native thread;
  8. 最后就是释放相关的资源(包括内存、锁等)

在上述过程,穿插着各种的判断检测,其中很大一部分都是关于各种层次下的线程的状态的检测,在JVM中,无论哪种层次的线程,都只允许执行一次。

最后

本文的参考资料比较多,但是由于我在写作的时候没有进行做引用记录,于是就不一一列举了。但是,所有的参考引用,我在文章的引用位置都以超链接的方式放出来了。
写作相当辛苦,所以能不能不要抄袭/剽窃我的劳动成果,谢谢。
若需转载授权,在评论留言即可。


未经授权,不得转载。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容

  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,572评论 18 399
  • 本文主要讲了java中多线程的使用方法、线程同步、线程数据传递、线程状态及相应的一些线程函数用法、概述等。 首先讲...
    李欣阳阅读 2,438评论 1 15
  • Java多线程学习 [-] 一扩展javalangThread类 二实现javalangRunnable接口 三T...
    影驰阅读 2,949评论 1 18
  • 该文章转自:http://blog.csdn.net/evankaka/article/details/44153...
    加来依蓝阅读 7,331评论 3 87
  • Part2 Chapter1 2017.3.28 看到Winston暗自揣测纸条的内容时,我差点要惊呆,本以为...
    七月米线汤阅读 176评论 0 0