舉個栗子:http://www.lxweimin.com/p/79f5e0bcd652
線程調用start()方法后便會run()起來,但是為什么呢,加個斷點debug一下來看看.
- 首先執行Thread.start()方法,來看源碼:
public synchronized void start() {
if (threadStatus != 0)
throw new IllegalThreadStateException();
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
}
}
}
- 方法首先判斷threadStatus變量值,來看這個變量的聲明
private volatile int threadStatus = 0;
//volatile是一個類型修飾符(type specifier).volatile的作用是作為指令[關鍵字],確保本條指令不會因[編譯器]的優化而省略,且要求每次直接讀值。
看名字應該是表示線程狀態的一個狀態,默認為0.非零的話拋出一個非法線程狀態的異常.
- 然后向ThreadGroup的group添加當前線程對象.
- 定義一個Boolean變量started標記
- 然后執行start0方法,執行完以后控制臺就已經有輸出了
看來關鍵就是這個start0方法
來看是聲明
private native void start0();
(以下過程比較復雜,像我這樣的初學者可以直接拖到問尾大致了解一下)
(以下部分涉及轉載,原文地址:https://www.ibm.com/developerworks/cn/java/j-lo-processthread/#icomments
)
- Thread 類有個 registerNatives 本地方法,該方法主要的作用就是注冊一些本地方法供 Thread 類使用,如 start0(),stop0() 等等,可以說,所有操作本地線程的本地方法都是由它注冊的 . 這個方法放在一個 static 語句塊中,這就表明,當該類被加載到 JVM 中的時候,它就會被調用,進而注冊相應的本地方法。
private static native void registerNatives();
static{
registerNatives();
}
- 本地方法 registerNatives 是定義在 Thread.c 文件中的。Thread.c 是個很小的文件,定義了各個操作系統平臺都要用到的關于線程的公用數據和操作.
JNIEXPORT void JNICALL
Java_Java_lang_Thread_registerNatives (JNIEnv *env, jclass cls){
(*env)->RegisterNatives(env, cls, methods, ARRAY_LENGTH(methods));
}
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},
};
- 到此,可以容易的看出 Java 線程調用 start 的方法,實際上會調用到 JVM_StartThread 方法,那這個方法又是怎樣的邏輯呢。實際上,我們需要的是(或者說 Java 表現行為)該方法最終要調用 Java 線程的 run 方法,事實的確如此。 在 jvm.cpp 中,有如下代碼段:
JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))
…
native_thread = new JavaThread(&thread_entry, sz);
…
- 這里JVM_ENTRY是一個宏,用來定義JVM_StartThread 函數,可以看到函數內創建了真正的平臺相關的本地線程,其線程函數是 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()),
vmSymbolHandles::run_method_name(),
vmSymbolHandles::void_method_signature(),THREAD);
}
- 可以看到調用了 vmSymbolHandles::run_method_name 方法,這是在 vmSymbols.hpp 用宏定義的:
class vmSymbolHandles: AllStatic {
…
template(run_method_name,"run")
…
}
-
至于 run_method_name 是如何聲明定義的,因為涉及到很繁瑣的代碼細節,本文不做贅述。感興趣的讀者可以自行查看 JVM 的源代碼。
圖一 綜上所述,Java 線程的創建調用過程如 圖 1 所示,首先 , Java 線程的 start 方法會創建一個本地線程(通過調用 JVM_StartThread),該線程的線程函數是定義在 jvm.cpp 中的 thread_entry,由其再進一步調用 run 方法。可以看到 Java 線程的 run 方法和普通方法其實沒有本質區別,直接調用 run 方法不會報錯,但是卻是在當前線程執行,而不會創建一個新的線程。