背景知識
文中參考文章鏈接:
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()
方法。到此,整個線程的創建和啓動流程就完成了。