终于搞懂线程的启动流程了

背景知识

文中参考文章链接:

1、线程创建方式-继承 Thread 类、实现 Runnable 接口、Callable接口
2、openJDK_HotSpot源码下载

前言

在 Java 中,有一句比较流行的话就是万物皆对象,同样的在多线程中,我觉得有一句话也必将贴切, 那就是线程皆Thread。Thread是多线程的根本,在java中,不管是什么方式创建的线程(在上一篇介绍的三种线程创建方式),它的开启都是都是始于Thread的start()方法。

​ 调用start()方法去启动一个线程,当 run 方法中的代码执行完毕以后,线程的生命周期也将终止。通过调用 start()方法的意思是当前线程告诉 JVM,启动调用 start()方法的线程。 下面我们来分析一下启动的原理。

1、java线程创建

1.1 start()

有一些初学者在学习线程的时候会比较疑惑,启动一个线程为什么是调用 start()方法,而不是 run 方法,在此做一个简单的分析,我们先简单看一下 start()方法的定义 :

    /**
     * Causes this thread to begin execution; the Java Virtual Machine
     * calls the <code>run</code> method of this thread.
     * start方法将导致this thread开始执行。由JVM调用this thread的run方法
     *
     * <p>
     * The result is that two threads are running concurrently: the
     * current thread (which returns from the call to the
     * <code>start</code> method) and the other thread (which executes its
     * <code>run</code> method).
     * <p>
     *
     * 结果是 调用start方法的当前线程 和 执行run方法的另一个线程 同时运行。
     *
     * It is never legal to start a thread more than once.
     * In particular, a thread may not be restarted once it has completed
     * execution.
     *多次启动线程永远不合法。 特别是,线程一旦完成执行就不会重新启动
     *
     * @exception  IllegalThreadStateException  if the thread was already
     *               started.
     * @see        #run()
     * @see        #stop()
     */
    public synchronized void `start()` {
        /**
         * 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.
         *对于由VM创建/设置的main方法线程或“system”组线程,不会调用此方法。未来添加到此方法的任何新功能可能也必须添加到VM中
         *
         * A zero status value corresponds to state "NEW".
         *status=0 代表是 status 是 "NEW"。
         */
        //1、判断线程的转态
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
        //2、通知组该线程即将启动,以便将其添加到线程组的列表中;并且减少线程组的未启动线程数递减
        group.add(this);

        boolean started = false;
        try {
            //3、调用native方法,底层开启异步线程,并调用run方法
            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 */
//                忽略异常。 如果start0抛出一个Throwable,它将被传递给调用堆栈
            }
        }
    }
    //native方法,JVM创建并启动线程,并调用run方法
    private native void start0();

通过代码需要注意一下几点:

1)start方法用synchronized修饰,为同步方法;
2)虽然为同步方法,但不能避免多次调用问题,用threadStatus来记录线程状态,如果线程被多次	start会抛出异常;threadStatus的状态由JVM控制;
3)使用Runnable时,主线程无法捕获子线程中的异常状态。线程的异常,应在线程内部解决。

1.2 start0()

​ 接着,我们从源码看到调用 start 方法实际上是调用一个 native 方法start0() 来启动一个线程,首先 start0() 这个方法是在Thread 的静态块中来注册的,代码如下 :

public class Thread implements Runnable {
    /* Make sure registerNatives is the first thing <clinit> does. */
    private static native void registerNatives();
    static {
        registerNatives();
    }
}

register Natives 的本地方法的定义在文件 Thread.c,它定义了各个操作系统平台要用的关于线程的公共数据和操作,以下是 Thread.c 的全部内容。链接地址:
http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/00cd9dc3c2b5/src/share/native/java/lang/Thread.c

static JNINativeMethod methods[] = {
    #线程启动调用start0()
    {"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},
};

#undef THD
#undef OBJ
#undef STE

JNIEXPORT void JNICALL
Java_java_lang_Thread_registerNatives(JNIEnv *env, jclass cls)
{
    (*env)->RegisterNatives(env, cls, methods, ARRAY_LENGTH(methods));
}

从 这 段代码可以看出 , start0() 实 际 会 执 行 JVM_StartThread 方法,这个方法是干吗的呢?从名字上来看,似乎是在 JVM 层面去启动一个线程,如果真的是这样,那么在 JVM 层面,一定会调用 Java 中定义的 run 方法。那接下来继续去找找答案。

2、jvm创建线程

上一节是在java层面使用start()方法启动线程,实际上此时还并未创建线程,线程的创建和启动都是由jvm来调用操作系统的指令进行创建和启动的,接下来我们看一下jvm中是如何创建线程,启动线程的。

2.1 JVM_StartThread

先找到 jvm.cpp 这个文件,这个文件可以在 hotspot 的源码中找到,如果愿意深究源码的,可以参考背景知识中,Hotspot源码下载地址,进行源码下载。

我们接着看下 jvm.cpp中JVM_StartThread方法的定义。

JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))
  JVMWrapper("JVM_StartThread");
  JavaThread *native_thread = NULL;
......省略........ 
 //1、创建本地线程
 native_thread = new JavaThread(&thread_entry, sz);
......省略........
  //2、启动线程
  Thread::start(native_thread);

JVM_END

JVM_ENTRY 是用来定义JVM_Start Thread函数的,在这个函数里面创建了一个真正和平台有关的本地线程。创建本地线程调用的是native_thread = new JavaThread(&thread_entry, sz);,这里的参数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);
}

其实就是通过回调方法调用Java的线程中定义的run方法,此处是个宏定义,在vmSymbols.hpp文件中可以找到如下代码:

#define VM_SYMBOLS_DO(template, do_alias)   
template(run_method_name,"run")   //回调的是run方法

这里的接着看new 出来的JavaThread是什么?

2.2 JavaThread

从thread.cpp中找到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);
  _safepoint_visible = false;
}

此方法有两个参数:

  • 1)entry_point:表示函数名称,线程创建成功之后会根据这个函数名称调用对应的函数,也就是run()方法;

  • 2)stack_sz:表示当前进程内已经有的线程数量。

    下面,我们重点关注与一下 os::create_thread这个方法,它实际就是调用平台创建线程的方法,它会根据不同的操作系统去创建线程。

2.3 os::create_thread

上一步的时候,我们说过了,它会根据不同的操作系统调用不同的创建线程的方式,本文就以Linux操作系统为例,打开os_linux.cpp可以找到os::create_thread方法

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

  // Allocate the OSThread object
  OSThread* osthread = new OSThread(NULL, NULL);

 ........................

    pthread_t tid;
    //java_start方法重点看
    int ret = pthread_create(&tid, &attr, (void* (*)(void*)) java_start, thread);
}

2.4 java_start

接着看下·java_start方法:

static void *java_start(Thread *thread) {
  。。。。。。。。。。。。。。
  // handshaking with parent thread
  {
    MutexLockerEx ml(sync, Mutex::_no_safepoint_check_flag);

    // notify parent thread
     //1、设置初始化状态
    osthread->set_state(INITIALIZED);
      //2、唤醒所有线程
    sync->notify_all();

    // wait until os::start_thread()
    //3、不停的查看线程的当前状态是不是Initialized, 如果是的话,调用了sync->wait()的方法等待。
    while (osthread->get_state() == INITIALIZED) {
      sync->wait(Mutex::_no_safepoint_check_flag);
    }
  }

  // call one more level start routine
    //4、被唤醒后执行run方法
  thread->run();

  return 0;
}

此方法主要包含几个流程;

1、jvm先设置了当前线程的状态是Initialized;

2、用notify_all()唤醒所有的线程;

3、查看当前线程是不是Initialized状态,如果是的话,调用sync->wait()的方法进行等待;

4、thread->run()要等到被唤醒才能执行。

3、启动线程

3.1 Thread::start

根据2.1节中JVM_StartThread方法定义的,在线程创建之后,就会执行Thread::start(native_thread),用来启动线程,启动线程会调用 Thread.cpp 文件中的Thread::start(Thread* thread)方法。如下:

void Thread::start(Thread* thread) {
  trace("start", thread);
  // Start is different from resume in that its safety is guaranteed by context or
  // being called from a Java method synchronized on the Thread object.
  if (!DisableStartThread) {
    if (thread->is_Java_thread()) {
      // Initialize the thread state to RUNNABLE before starting this thread.
      // Can not set it after the thread started because we do not know the
      // exact thread state at that time. It could be in MONITOR_WAIT or
      // in SLEEPING or some other state.
      java_lang_Thread::set_thread_status(((JavaThread*)thread)->threadObj(),
                                          java_lang_Thread::RUNNABLE);
    }
  //根据不同的操作系统进行线程的启动
    os::start_thread(thread);
  }
}

start 方法中会 先判断是否为Java线程,如果是java线程会将线程的状态设置为RUNNABLE,接着调用os::start_thread(thread),调用平台启动线程的方法。

3.2 os::start_thread

在os.cpp中,可以找到os::start_thread方法,

void os::start_thread(Thread* thread) {
  // guard suspend/resume
  MutexLockerEx ml(thread->SR_lock(), Mutex::_no_safepoint_check_flag);
  OSThread* osthread = thread->osthread();
  osthread->set_state(RUNNABLE);
  pd_start_thread(thread);
}

该方法设置了线程的状态为RUNNABLE,但没有notify线程,然后又调用了os_linux.cpp中的pd_start_thread(thread)

void os::pd_start_thread(Thread* thread) {
  OSThread * osthread = thread->osthread();
  assert(osthread->get_state() != INITIALIZED, "just checking");
  Monitor* sync_with_child = osthread->startThread_lock();
  MutexLockerEx ml(sync_with_child, Mutex::_no_safepoint_check_flag);
  sync_with_child->notify();
}

此时notify了线程,因为这时候的线程的状态是RUNNABLE, 线程被唤醒后,2.4小节中的Java_start方法继续往下执行,于是调用了thread->run()的方法。

4、线程回调

4.1 JavaThread::run()

接着来看一下 Thread.cpp 文件中的 JavaThread::run()方法 。

// The first routine called by a new Java thread
void JavaThread::run() {
  // initialize thread-local alloc buffer related fields
  this->initialize_tlab();

。。。。。。。。。。。。

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

}

这个方法中主要是做一系列的初始化操作,最后调用了thread_main_inner方法:

void JavaThread::thread_main_inner() {
  assert(JavaThread::current() == this, "sanity check");
  assert(this->threadObj() != NULL, "just checking");
 .............
     //
    this->entry_point()(this, this);
  }

  DTRACE_THREAD_PROBE(stop, this);

  this->exit(false);
  delete this;
}

方法中调用this->entry_point()(this, this), 在2.2小节中说过entry_point 是一个函数名,线程创建成功后会调用这个函数,这个函数就是在2.1节中定义的run()方法。

#define VM_SYMBOLS_DO(template, do_alias)   
template(run_method_name,"run")   //回调的是run方法

这也就是为什么在线程启动后,会回调线程里复写的run()方法。

5 小结

线程创建流程图:
在这里插入图片描述

以上通过Hotspot源码对线程的创建和启动做了详细的讲解,我们可以清楚的了解到了线程创建和启动的流程和原理,下面对以上过程做一个简单的总结:

  • 1)使用new Thread()创建一个线程,然后调用start()方法进行java层面的线程启动;

  • 2)调用本地方法start0(),去调用jvm中的JVM_StartThread方法进行线程创建和启动;

  • 3)调用new JavaThread(&thread_entry, sz)进行线程的创建,并根据不同的操作系统平台调用对应的os::create_thread方法进行线程创建;

  • 4)新创建的线程状态为Initialized,调用了sync->wait()的方法进行等待,等到被唤醒才继续执行thread->run();;

  • 5)调用Thread::start(native_thread);方法进行线程启动,此时将线程状态设置为RUNNABLE,接着调用os::start_thread(thread),根据不同的操作系统选择不同的线程启动方式;

  • 6)线程启动之后状态设置为RUNNABLE, 并唤醒第4步中等待的线程,接着执行thread->run()的方法;

  • 7)JavaThread::run()方法会回调第1步new Thread中复写的run()方法。

    到此,整个线程的创建和启动流程就完成了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章