從Java到C++,以JVM的角度看Java線程的創(chuàng)建與運(yùn)行

寫在前面

寫作時間:2017.5
本文JDK版本:JDK 1.8
本文簡述:從Java的新建一個線程開始,溯源到Thread類的源碼浙巫,然后再從Thread類的源碼跳到虛擬機(jī)層的C+ +源碼莽鸭。虛擬機(jī)層是平臺相關(guān)的吗伤,但我沒有對每個平臺的實(shí)現(xiàn)機(jī)制都探索一遍,于是只選取Linux部分的實(shí)現(xiàn)源碼進(jìn)行探究蒋川。而C++的代碼在邏輯上又分為幾層牲芋,逐層深挖,一直到C++新建的線程調(diào)用Java線程的run()方法結(jié)束捺球。(本文每處都貼有源碼地址缸浦,可作參考)
本文持續(xù)更新地址:http://www.reibang.com/p/3ce1b5e5a55e

附:
本文相當(dāng)長,可以先看結(jié)尾的總結(jié)再回頭看正文氮兵,如果你只想理解個大概裂逐,也可以只看java部分以及結(jié)尾總結(jié)。
可能我的文筆以及實(shí)力都有所欠缺泣栈,想要完全理解的話可能需要多看幾遍以及花點(diǎn)時間卜高,請諸君包涵。(但做學(xué)問需要的不正是沉得住氣南片,耐得住寂寞嗎)
然而掺涛,代碼細(xì)節(jié)實(shí)在是太多了,硬吃實(shí)在不是個辦法疼进,所以我的建議是薪缆,最開始可以先不看代碼,只看每個小節(jié)后面的總結(jié)和代碼中的中文注釋伞广,甚至是先看流程終結(jié)拣帽,再看小節(jié)的總結(jié)疼电,最后有興趣才開始研究源碼。
切忌“只見樹木减拭,不見森林”

線程的創(chuàng)建

故事的開始蔽豺,是這樣的一段的代碼:

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

然后我們的目標(biāo)就是,在C++源碼中找到這個run()方法是在哪里執(zhí)行的拧粪。
首先進(jìn)入的是它的構(gòu)造方法:

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

經(jīng)過一系列的調(diào)用修陡,最終跳到這個方法,(這里的代碼有點(diǎn)長既们,就不給出完整代碼濒析,完整的源碼在這里:源碼傳送門

/**
     * Initializes a Thread.
     *
     * @param g 線程組
     * @param target 攜帶run方法的Runnable對象
     * @param name 線程名
     * @param stackSize  新線程的需要的stack size,若此值為0則表明此屬性忽略
     * @param acc 將要被繼承的AccessControlContext啥纸,若此值為null号杏,則此屬性默認(rèn)設(shè)置為
     *            AccessController.getContext()
     * @param inheritThreadLocals 若此值為true,從構(gòu)造此線程的線程中繼承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();                  
    }

稍微嘗試著總結(jié)一下:

  1. 設(shè)置線程名(即設(shè)置name屬性)斯棒,一般情況下盾致,這個name的格式是“Thread-X”,X是一個數(shù)字,代表用這個線程類初始化的第X個實(shí)例
  2. 設(shè)置線程的ThreadGroup荣暮,這個ThreadGroup要么從應(yīng)用的SecurityManager處拿到庭惜,要么就直接繼承父線程的ThreadGroup。(設(shè)置完成以后還會有一個驗(yàn)證)
  3. 在真正設(shè)置此線程的ThreadGroup之前穗酥,需要為這個ThreadGroup的“還未開始的線程數(shù)目”(nUnstartedThreads)加一
  4. 設(shè)置此線程是否為守護(hù)線程(即設(shè)置daemon屬性)护赊,若父線程為守護(hù)線程,那么此線程也是守護(hù)線程
  5. 設(shè)置此線程的優(yōu)先級(即priority屬性)為父線程的優(yōu)先級(順便補(bǔ)充一句砾跃,子線程的優(yōu)先級是不能大于父線程的)
  6. 設(shè)置此線程的上下文類加載器
  7. 設(shè)置這個Thread的target骏啰,這個target就是攜帶了我們寫好的run()方法的Runnable對象
  8. 設(shè)置stackSize
  9. 設(shè)置線程ID(tid屬性)

關(guān)于run方法的一個補(bǔ)充

或許有人會說,我創(chuàng)建線程的時候抽高,是使用傳遞一個Runnable對象來進(jìn)行創(chuàng)建對象判耕,并沒有重寫原來的run方法,那么到了這里翘骂,還調(diào)用run方法的話壁熄,不就出問題了。
其實(shí)碳竟,這個問題我們看一下Thread.java的run()方法就好了:

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

target是什么草丧,target就是我們在初始化Thread時傳遞進(jìn)去的Runnable對象,那么這幾行代碼可以解釋我們的幾個疑問:

  1. 如果我們是通過傳遞Runnable對象來初始化Thread實(shí)例的話莹桅,那么就是將實(shí)例的run方法“替換”為Runnable對象的run()方法(邏輯上的替換)
  2. 如果我們的Thread繼承類重寫了run()方法昌执,也就是說原本的“替換”邏輯被覆蓋了,這時我們再通過傳遞Runnable對象來初始化Thread實(shí)例的話,這個實(shí)例的run不會再被替換仙蚜,也就是這個實(shí)例僅會執(zhí)行我們重寫的run方法。

線程的開始

我們都知道厂汗,開始一個線程就是調(diào)用這個線程的start方法:

    /**
     * 調(diào)用此方法將會使得thread開始運(yùn)行委粉,JVM會在這個線程中調(diào)用run方法。
     * <p>
     * 調(diào)用結(jié)果是娶桦,兩個線程同時運(yùn)行:調(diào)用此線程的線程贾节,以及此線程
     * <p>
     * 無論在什么情況下,要求同一條線程開始兩次都是不合法衷畦,即便是此線程已經(jīng)結(jié)束在要求開始也是不合法的栗涂。
     *
     * @exception  IllegalThreadStateException  若此線程曾經(jīng)開始(start)過
     * @see        #run()
     * @see        #stop()
     */
    public synchronized void start() {
        /**
         * 
         * 對于main方法線程(即運(yùn)行main方法的線程),或者是由虛擬機(jī)創(chuàng)建的“system”組的
         * 線程祈争,都不需要調(diào)用此方法來啟動斤程。
         * 任何在這個方法中添加的新功能,以后都有可能添加到虛擬機(jī)上菩混。
         * 翻譯的不是很滿意忿墅,原文留在這里:
         * 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,則表明這個狀態(tài)是NEW狀態(tài)
         */
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        /* 
         * 通知線程組:此線程已經(jīng)就緒沮峡,可以開始運(yùn)行了
         * 所以疚脐,這條線程可以添加到線程組的線程list中,以及線程組
         * 未開始線程的數(shù)目(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 */
            }
        }
    }

挺簡短的一段代碼棍弄,我們可以總結(jié)出以下步驟:

第一步,非常重要疟游,就是先檢查線程的狀態(tài)呼畸,如果線程的狀態(tài)不是NEW狀態(tài)的話,那么就會拋出一個異常(關(guān)于Java線程的狀態(tài)乡摹,可以看這篇文章:Java 線程的幾種狀態(tài)
第二步役耕,將這個Thread添加到之前引用的ThreadGroup中
第三步,調(diào)用native start0()

關(guān)于Thread類中的native方法

在Thread.java里面聪廉,有一個registerNatives()的native方法瞬痘,并且在Threa.java的第一個static塊中就調(diào)用了這個方法,保證這個方法在類加載中是第一個被調(diào)用的方法板熊。這個native方法的作用是為其他native方法注冊到JVM中(可見:JVM查找java native方法的規(guī)則)框全。
通過這個方法,我們可以找到Thrad.java中native方法在JVM中對應(yīng)方法:
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干签、在進(jìn)入C之前的準(zhǔn)備

我們將會需要使用到這幾個概念:

java.lang.Thread: 這個是Java語言里的線程類津辩,由這個Java類創(chuàng)建的instance都會 1:1 映射到一個操作系統(tǒng)的osthread;
JavaThread: JVM中C++定義的類,一個JavaThread的instance代表了在JVM中的java.lang.Thread的instance, 它維護(hù)了線程的狀態(tài),并且維護(hù)一個指針指向java.lang.Thread創(chuàng)建的對象(oop)喘沿。它同時還維護(hù)了一個指針指向?qū)?yīng)的OSThread闸度,來獲取底層操作系統(tǒng)創(chuàng)建的osthread的狀態(tài)
OSThread: JVM中C++定義的類,代表了JVM中對底層操作系統(tǒng)的osthread的抽象蚜印,它維護(hù)著實(shí)際操作系統(tǒng)創(chuàng)建的線程句柄handle莺禁,可以獲取底層osthread的狀態(tài)
VMThread: JVM中C++定義的類,這個類和用戶創(chuàng)建的線程無關(guān)窄赋,是JVM本身用來進(jìn)行虛擬機(jī)操作的線程哟冬,比如GC

(引自:聊聊JVM(五)從JVM角度理解線程

補(bǔ)充:需要注意的是osthread和OSThread的區(qū)別,簡單的說忆绰,osthread是操作系統(tǒng)級別的線程浩峡,也就是真正意義上的線程,它的具體實(shí)現(xiàn)由操作系統(tǒng)完成错敢;
而OSThread則是關(guān)于osthread的抽象翰灾,通過操作系統(tǒng)暴露出來的接口(句柄)對osthread進(jìn)行操作,在實(shí)際的運(yùn)行中稚茅,OSThread會根據(jù)所運(yùn)行的操作系統(tǒng)(平臺)來創(chuàng)建预侯,也就是說,如果字節(jié)碼運(yùn)行的平臺是Linux的話峰锁,那么創(chuàng)建出來的OSThread就是Linux的OSThread萎馅;如果是Windows的話,那么創(chuàng)建出來的就是Windows的OSThread

0.5虹蒋、流程概述

為了避免“只見樹木糜芳,不見森林”,特意將流程總結(jié)提前到開始
撇開源碼魄衅,整個過程大概是:

  1. 在Java中峭竣,使用java.lang.Thread的構(gòu)造方法來構(gòu)建一個java.lang.Thread對象,此時只是對這個對象的部分字段(例如線程名晃虫,優(yōu)先級等)進(jìn)行初始化皆撩;
  2. 調(diào)用java.lang.Thread對象的start()方法,開始此線程哲银。此時扛吞,在start()方法內(nèi)部,調(diào)用start0() 本地方法來開始此線程荆责;
  3. start0()在VM中對應(yīng)的是JVM_StartThread滥比,也就是,在VM中做院,實(shí)際運(yùn)行的是JVM_StartThread方法(宏),在這個方法中盲泛,創(chuàng)建了一個JavaThread對象濒持;
  4. 在JavaThread對象的創(chuàng)建過程中,會根據(jù)運(yùn)行平臺創(chuàng)建一個對應(yīng)的OSThread對象寺滚,且JavaThread保持這個OSThread對象的引用柑营;
  5. 在OSThread對象的創(chuàng)建過程中,創(chuàng)建一個平臺相關(guān)的底層級線程村视,如果這個底層級線程失敗由境,那么就拋出異常;
  6. 在正常情況下蓖议,這個底層級的線程開始運(yùn)行,并執(zhí)行java.lang.Thread對象的run方法讥蟆;
  7. 當(dāng)java.lang.Thread生成的Object的run()方法執(zhí)行完畢返回后勒虾,或者拋出異常終止后,終止native thread;
  8. 最后就是釋放相關(guān)的資源(包括內(nèi)存瘸彤、鎖等)

在上述過程修然,穿插著各種的判斷檢測,其中很大一部分都是關(guān)于各種層次下的線程的狀態(tài)的檢測质况,在JVM中愕宋,無論哪種層次的線程,都只允許執(zhí)行一次结榄。

1中贝、從Java進(jìn)入C:JVM_StartThread

首先JVM會進(jìn)入jvm.cpp的jvm.cpp$JVM_StartThread 方法(確切地說,這個JVM_StartThread是一個宏):
關(guān)于native的函數(shù)參數(shù)

第一個參數(shù):JNIEnv* 是定義任意native函數(shù)的第一個參數(shù)(包括調(diào)用JNI的RegisterNatives函數(shù)注冊的函數(shù))臼朗,指向JVM函數(shù)表的指針邻寿,函數(shù)表中的每一個入口指向一個JNI函數(shù),每個函數(shù)用于訪問JVM中特定的數(shù)據(jù)結(jié)構(gòu)视哑。
第二個參數(shù):調(diào)用java中native方法的實(shí)例或Class對象绣否,如果這個native方法是實(shí)例方法,則該參數(shù)是jobject挡毅,如果是靜態(tài)方法蒜撮,則是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不會被提前釋放
    //(譯者附:)從上面的一個左花括號開始跪呈,一直到下一個匹配的右花括號為止段磨,都上了關(guān)于Threads_lock的互斥鎖
    //可以理解為Java的sync塊
    MutexLocker mu(Threads_lock);
    

    // 安全原因,先進(jìn)行一次檢測耗绿,個人猜測是薇溃,判斷這個線程是否已被啟動過,若是已啟動過缭乘,拋線程狀態(tài)異常
    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結(jié)構(gòu)體分配內(nèi)存沐序,以及創(chuàng)建一個本地線程琉用。這個從Java取過來的stack size是有一個符號數(shù),
      // 但構(gòu)造器需要的size_t是一個無符號數(shù)策幼,所以在轉(zhuǎn)換的時候需要避免因?yàn)閟ize是負(fù)數(shù)出現(xiàn)的問題邑时。
      size_t sz = size > 0 ? (size_t) size : 0;
      native_thread = new JavaThread(&thread_entry, sz);
      // 創(chuàng)建一個JavaThrea對象(創(chuàng)建過程見第二節(jié)),其中第二個參數(shù)thread_entry是一個方法指針特姐,詳見本節(jié)下方補(bǔ)充
      // ***************** 注意這個thread_entry  ************************
      // ***************** 注意這個thread_entry  ************************
      // ***************** 注意這個thread_entry  ************************

      // 仍做一次安全檢測晶丘,因?yàn)閛sthread不一定能創(chuàng)建成功(詳見下文第三節(jié)的osthread創(chuàng)建)
      if (native_thread->osthread() != NULL) {
        // 注意:當(dāng)前線程在下面的“prepare”沒有被使用到。
        native_thread->prepare(jthread);
      }
    }
  }

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

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

    //事實(shí)上唐含,如果native_thread的osthread為NULL浅浮,就可以宣告整個線程創(chuàng)建過程失敗
    //這時應(yīng)該整理內(nèi)存,并通知java層線程創(chuàng)建失敗
  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

關(guān)于thread_entry方法的補(bǔ)充:注意捷枯,這個方法非常重要滚秩,后面還會出現(xiàn),所以我們留到后面再解釋【第6節(jié)】淮捆。


然后我們來看一下JVM在這個宏里面做了什么:

  1. 申請一個鎖
  2. 創(chuàng)建一個JavaThread對象【見第二節(jié)】
  3. 如果有異常情況郁油,拋出異常
  4. 對第二步的JavaThread類,做一個準(zhǔn)備操作:thread.cpp$JavaThread::prepare
  5. 調(diào)用Thread::start方法攀痊,讓一條線程開始運(yùn)行

2桐腌、JavaThread對象的創(chuàng)建

本節(jié)是接上面小節(jié)中間,關(guān)于JavaThread對象創(chuàng)建的講述的苟径。
JavaThread是Java層的線程與平臺層線程中間的一個過渡:
一個java.lang.Thread對象案站,在其start方法中,會創(chuàng)建一個JavaThread對象棘街,由于java.lang.Thread對象的start方法只允許調(diào)用一次嚼吞,所以java.lang.Thread對象與JavaThread對象是一一對應(yīng)的
而在JavaThread對象的創(chuàng)建過程,會創(chuàng)建一個OSThread對象蹬碧,并且JavaThread對象會持有一個指向該OSThread對象的指針(至于什么是OSThread舱禽,大概是平臺相關(guān)的系統(tǒng)層線程,更詳細(xì)見第三節(jié))
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);
  // 在這里恩沽,由于內(nèi)存不足的原因誊稚,_osthread有可能為NULL、
  // 而我們應(yīng)當(dāng)拋出OutOfMenoryError罗心,然而現(xiàn)在我們還不能拋出Error
  // 因?yàn)榇朔椒ǖ恼{(diào)用者有可能扔持有著所有的鎖里伯,而我們必須釋放所有的鎖才能拋出后異常
  // (拋出異常的工作包括:創(chuàng)建并初始化一個exception對象,而初始化exception對象則必須要通過JavaCall離開VM渤闷,
  // 然后所有的鎖都必須要被釋放)
  //
  // 此時這條線程扔處在掛起的狀態(tài)疾瓮,而線程必須由創(chuàng)建者顯式地啟動!
  // 此外飒箭,線程還必須顯式地通過Threads:add方法被添加到Threads list 中狼电。
  // 上面的工作(顯式啟動蜒灰,顯式添加)之所以到達(dá)這里還沒完成,是因?yàn)榫€程還沒有被完成地初始化(可參見JVM_Start)
}

完成的操作有:

  1. 調(diào)用initialize方法對JavaThread進(jìn)行初始化肩碟,此方法僅是對結(jié)構(gòu)體的屬性做賦值强窖;
  2. 為這個JavaThread設(shè)置一個入口方法set_entry_point
  3. 根據(jù)平臺調(diào)用os::create_thread()方法來創(chuàng)建一個OSThread對象【見第三節(jié)】

3、根據(jù)平臺創(chuàng)建OSThread

在這一步中削祈,OSThread的創(chuàng)建是根據(jù)平臺來選擇翅溺,這里我是使用Linux部分的代碼來進(jìn)行研究的
OSThread是一個平臺相關(guān)線程,OSThread由JavaThread對象創(chuàng)建并進(jìn)行管理髓抑。
在OSThread創(chuàng)建的過程中咙崎,會通過pthread方法來創(chuàng)建一個真正意義上的底層級線程,

os_linux.cpp$os::create_thread
這個方法傳入三個參數(shù):
第一個是我們之前在JavaThread構(gòu)造方法中創(chuàng)建的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對象分配內(nèi)存
  OSThread* osthread = new OSThread(NULL, NULL);
  //(注:)關(guān)于這個構(gòu)造方法跛璧,并沒有做出特殊的操作严里,僅是初始化一些屬性,詳見下方補(bǔ)充
  
  if (osthread == NULL) {
    return false;
  }

  // 設(shè)置正確的狀態(tài)
  osthread->set_thread_type(thr_type);

  // 初始狀態(tài)應(yīng)該是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()) {
    // 如果調(diào)用者沒有指定stack size追城,那么就需要對這個stack size進(jìn)行計算
    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
    // (好吧刹碾,我也不是很懂這句注釋的意思,
    // 嘗試著直譯一下:如果我們正在運(yùn)行的是固定stack的LinuxThreads座柱,那么就序列化線程的創(chuàng)建過程)
    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下創(chuàng)建并執(zhí)行一個平臺層的線程的方法
    // 新建好的線程迷帜,應(yīng)該執(zhí)行的是java_start方法(即第二個參數(shù))
    //如果返回值是0,表示線程創(chuàng)建成功色洞,否則表示線程創(chuàng)建失敗,關(guān)于這個方法的詳細(xì)介紹戏锹,見下方補(bǔ)充
    
    pthread_attr_destroy(&attr);


    // 下面是平臺級線程創(chuàng)建失敗的一些處理
    // 如果osthread創(chuàng)建失敗,就把JAVAThread的osthread設(shè)置為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是剛才新建的底層級線程的一個標(biāo)識符火诸,我們需要通過這個標(biāo)識符來管理底層級線程
    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;
}

補(bǔ)充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);
}

補(bǔ)充創(chuàng)建系統(tǒng)級線程的pthread_create()方法

這個方法很長,做了以下的幾件事:
1盯荤、 創(chuàng)建一個osthread對象馋吗,并初始化這個對象,這個初始化包括:設(shè)置thread_type,設(shè)置線程狀態(tài)
2秋秤、讓我們傳進(jìn)來的JavaThread對象保存這個osthread對象的引用
3宏粤、中間還有一些設(shè)置線程棧數(shù)量脚翘,獲取鎖之類的操作
4、864行商架,int ret = pthread_create(&tid, &attr, (void* ()(void)) java_start, thread);這個方法是C++創(chuàng)建線程的庫方法堰怨,通過調(diào)用這個方法,會創(chuàng)建一個C++ 線程并使線程進(jìn)入就緒狀態(tài)蛇摸,即可以開始運(yùn)行
5备图、這個線程將執(zhí)行java_start這個方法【見第四節(jié)】
6、開始收尾赶袄,清理一下內(nèi)存

4揽涮、java_start方法

這個方法是OSThread創(chuàng)建的底層級線程中將要執(zhí)行的方法,它的描述是:為所有新建的線程啟動例程

os_linux.cpp$java_start

// 線程為所有新創(chuàng)建的線程啟動例程饿肺。
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();

  // 與父進(jìn)程進(jìn)行新號交互(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);
    }
  }

  // 調(diào)用一個更高級別的開始例程(見第五節(jié))
  thread->run();

  return 0;
}

這個方法做的事:
1蒋困、拿到這個線程的線程id
2、分配內(nèi)存敬辣?
3雪标、設(shè)置ThreadLocal
4、線程安全檢測
5溉跃、中間還有一些東西村刨,不想關(guān)心
6、同樣地撰茎,再次設(shè)置這個thread的osthrad的狀態(tài)
7嵌牺、調(diào)用Thread的run方法,但是這個run方法是個虛方法龄糊,實(shí)際上調(diào)用的是JavaThread的run方法【見第五節(jié)】

5逆粹、JavaThread::run()

JavThread::run():

// Java Thread第一個調(diào)用的例程
void JavaThread::run() {
    //初始化相關(guān)字段
  // 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();

    //線程基本初始化完成,在虛擬機(jī)中可以當(dāng)作是safepoint代碼來處理了
    //于是將線程的狀態(tài)從 _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檢查完這個新線程以后才能調(diào)用此方法
  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();

  // 注意僻弹,一旦運(yùn)行到這里,線程就不再合法(valid)了
}

這個方法完成的事有:
1他嚷、初始化相關(guān)字段
2奢方、將線程的狀態(tài)從 _thread_new切換到_thread_in_vm
3、調(diào)用thread_main_inner()方法【見第六節(jié)】
(這里說的safepoint應(yīng)該就是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;
}

然后又回到了之前的我們設(shè)置的entry_point():(可回看第一節(jié))
thread_entry方法的補(bǔ)充:

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(),非常關(guān)鍵鹊奖!

這個方法是在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")

也就是說设哗,這里調(diào)用的是java.lang.Thread對象的run()方法!

總結(jié)

撇開源碼两蟀,整個過程大概是:

  1. 在Java中网梢,使用java.lang.Thread的構(gòu)造方法來構(gòu)建一個java.lang.Thread對象,此時只是對這個對象的部分字段(例如線程名赂毯,優(yōu)先級等)進(jìn)行初始化战虏;
  2. 調(diào)用java.lang.Thread對象的start()方法,開始此線程党涕。此時烦感,在start()方法內(nèi)部,調(diào)用start0() 本地方法來開始此線程膛堤;
  3. start0()在VM中對應(yīng)的是JVM_StartThread手趣,也就是,在VM中肥荔,實(shí)際運(yùn)行的是JVM_StartThread方法(宏),在這個方法中绿渣,創(chuàng)建了一個JavaThread對象;
  4. 在JavaThread對象的創(chuàng)建過程中燕耿,會根據(jù)運(yùn)行平臺創(chuàng)建一個對應(yīng)的OSThread對象中符,且JavaThread保持這個OSThread對象的引用;
  5. 在OSThread對象的創(chuàng)建過程中缸棵,創(chuàng)建一個平臺相關(guān)的底層級線程舟茶,如果這個底層級線程失敗谭期,那么就拋出異常堵第;
  6. 在正常情況下,這個底層級的線程開始運(yùn)行隧出,并執(zhí)行java.lang.Thread對象的run方法踏志;
  7. 當(dāng)java.lang.Thread生成的Object的run()方法執(zhí)行完畢返回后,或者拋出異常終止后胀瞪,終止native thread;
  8. 最后就是釋放相關(guān)的資源(包括內(nèi)存针余、鎖等)

在上述過程,穿插著各種的判斷檢測凄诞,其中很大一部分都是關(guān)于各種層次下的線程的狀態(tài)的檢測圆雁,在JVM中,無論哪種層次的線程帆谍,都只允許執(zhí)行一次伪朽。

最后

本文的參考資料比較多,但是由于我在寫作的時候沒有進(jìn)行做引用記錄汛蝙,于是就不一一列舉了烈涮。但是,所有的參考引用,我在文章的引用位置都以超鏈接的方式放出來了煤惩。
寫作相當(dāng)辛苦笛质,所以能不能不要抄襲/剽竊我的勞動成果,謝謝讶舰。
若需轉(zhuǎn)載授權(quán)鞍盗,在評論留言即可。


未經(jīng)授權(quán)绘雁,不得轉(zhuǎn)載橡疼。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市庐舟,隨后出現(xiàn)的幾起案子欣除,更是在濱河造成了極大的恐慌,老刑警劉巖挪略,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件历帚,死亡現(xiàn)場離奇詭異,居然都是意外死亡杠娱,警方通過查閱死者的電腦和手機(jī)挽牢,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來摊求,“玉大人禽拔,你說我怎么就攤上這事∈也妫” “怎么了睹栖?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長茧痕。 經(jīng)常有香客問我野来,道長,這世上最難降的妖魔是什么踪旷? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任曼氛,我火速辦了婚禮,結(jié)果婚禮上令野,老公的妹妹穿的比我還像新娘舀患。我一直安慰自己,他們只是感情好气破,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布聊浅。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪狗超。 梳的紋絲不亂的頭發(fā)上弹澎,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天,我揣著相機(jī)與錄音努咐,去河邊找鬼苦蒿。 笑死,一個胖子當(dāng)著我的面吹牛渗稍,可吹牛的內(nèi)容都是我干的佩迟。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼竿屹,長吁一口氣:“原來是場噩夢啊……” “哼报强!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起拱燃,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤秉溉,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后碗誉,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體召嘶,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年哮缺,在試婚紗的時候發(fā)現(xiàn)自己被綠了弄跌。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡尝苇,死狀恐怖铛只,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情糠溜,我是刑警寧澤淳玩,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站诵冒,受9級特大地震影響凯肋,放射性物質(zhì)發(fā)生泄漏谊惭。R本人自食惡果不足惜汽馋,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望圈盔。 院中可真熱鬧豹芯,春花似錦、人聲如沸驱敲。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽众眨。三九已至握牧,卻和暖如春容诬,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背沿腰。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工览徒, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人颂龙。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓习蓬,卻偏偏與公主長得像,于是被迫代替她去往敵國和親措嵌。 傳聞我的和親對象是個殘疾皇子躲叼,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法企巢,內(nèi)部類的語法枫慷,繼承相關(guān)的語法,異常的語法浪规,線程的語...
    子非魚_t_閱讀 31,639評論 18 399
  • 本文主要講了java中多線程的使用方法流礁、線程同步、線程數(shù)據(jù)傳遞罗丰、線程狀態(tài)及相應(yīng)的一些線程函數(shù)用法神帅、概述等。 首先講...
    李欣陽閱讀 2,456評論 1 15
  • Java多線程學(xué)習(xí) [-] 一擴(kuò)展javalangThread類 二實(shí)現(xiàn)javalangRunnable接口 三T...
    影馳閱讀 2,959評論 1 18
  • 該文章轉(zhuǎn)自:http://blog.csdn.net/evankaka/article/details/44153...
    加來依藍(lán)閱讀 7,353評論 3 87
  • Part2 Chapter1 2017.3.28 看到Winston暗自揣測紙條的內(nèi)容時萌抵,我差點(diǎn)要驚呆找御,本以為...
    七月米線湯閱讀 178評論 0 0