終於搞懂線程的啓動流程了

背景知識

文中參考文章鏈接:

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()方法。

    到此,整個線程的創建和啓動流程就完成了。

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