標籤:result system 入口 get isa lin 查看 兩種 start
Java 建立線程的方法
實際上,建立線程最重要的是提供線程函數(回呼函數),該函數作為新建立線程的入口函數,實現自己想要的功能。Java 提供了兩種方法來建立一個線程:
繼承 Thread 類
class MyThread extends Thread{
public void run() {
System.out.println("My thread is started.");
實現該繼承類的 run 方法,然後就可以建立這個子類的對象,調用 start 方法即可建立一個新的線程:
MyThread myThread = new MyThread();
myThread.start();
實現 Runnable 介面
class MyRunnable implements Runnable{
public void run() {
System.out.println("My runnable is invoked.");
實現 Runnable 介面的類的對象可以作為一個參數傳遞到建立的 Thread 對象中,同樣調用 Thread#start 方法就可以在一個新的線程中運行 run 方法中的代碼了。
Thread myThread = new Thread( new MyRunnable());
myThread.start();
1
2
可以看到,不管是用哪種方法,實際上都是要實現一個 run 方法的。 該方法本質是上一個回調方法。由 start 方法新建立的線程會調用這個方法從而執行需要的代碼。 從後面可以看到,run 方法並不是真正的線程函數,只是被線程函數調用的一個 Java 方法而已,和其他的 Java 方法沒有什麼本質的不同。
Java 線程的實現
從概念上來說,一個 Java 線程的建立根本上就對應了一個本地線程(native thread)的建立,兩者是一一對應的。 問題是,本地線程執行的應該是本地代碼,而 Java 線程提供的線程函數是 Java 方法,編譯出的是 Java 位元組碼,所以可以想象的是, Java 線程其實提供了一個統一的線程函數,該線程函數通過 JAVA 虛擬機器調用 Java 線程方法 , 這是通過 Java 本地方法調用來實現的。
以下是 Thread#start 方法的樣本:
public synchronized void start() {
…
start0();
可以看到它實際上調用了本地方法 start0, 該方法的聲明如下:
private native void start0();
1
Thread 類有個 registerNatives 本地方法,該方法主要的作用就是註冊一些本地方法供 Thread 類使用,如 start0(),stop0() 等等,可以說,所有操作本地線程的本地方法都是由它註冊的 . 這個方法放在一個 static 語句塊中,這就表明,當該類被載入到 JVM 中的時候,它就會被調用,進而註冊相應的本地方法。
private static native void registerNatives();
static{
registerNatives();
本地方法 registerNatives 是定義在 Thread.c 檔案中的。Thread.c 是個很小的檔案,定義了各個作業系統平台都要用到的關於線程的公用資料和操作,如代碼清單 1 所示。
清單1
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 ")[["Java 建立線程的方法
實際上,建立線程最重要的是提供線程函數(回呼函數),該函數作為新建立線程的入口函數,實現自己想要的功能。Java 提供了兩種方法來建立一個線程:
繼承 Thread 類
class MyThread extends Thread{
public void run() {
System.out.println("My thread is started.");
}
}
1
2
3
4
5
實現該繼承類的 run 方法,然後就可以建立這個子類的對象,調用 start 方法即可建立一個新的線程:
MyThread myThread = new MyThread();
myThread.start();
1
2
實現 Runnable 介面
class MyRunnable implements Runnable{
public void run() {
System.out.println("My runnable is invoked.");
}
}
1
2
3
4
5
實現 Runnable 介面的類的對象可以作為一個參數傳遞到建立的 Thread 對象中,同樣調用 Thread#start 方法就可以在一個新的線程中運行 run 方法中的代碼了。
Thread myThread = new Thread( new MyRunnable());
myThread.start();
1
2
可以看到,不管是用哪種方法,實際上都是要實現一個 run 方法的。 該方法本質是上一個回調方法。由 start 方法新建立的線程會調用這個方法從而執行需要的代碼。 從後面可以看到,run 方法並不是真正的線程函數,只是被線程函數調用的一個 Java 方法而已,和其他的 Java 方法沒有什麼本質的不同。
Java 線程的實現
從概念上來說,一個 Java 線程的建立根本上就對應了一個本地線程(native thread)的建立,兩者是一一對應的。 問題是,本地線程執行的應該是本地代碼,而 Java 線程提供的線程函數是 Java 方法,編譯出的是 Java 位元組碼,所以可以想象的是, Java 線程其實提供了一個統一的線程函數,該線程函數通過 JAVA 虛擬機器調用 Java 線程方法 , 這是通過 Java 本地方法調用來實現的。
以下是 Thread#start 方法的樣本:
public synchronized void start() {
…
start0();
…
}
1
2
3
4
5
可以看到它實際上調用了本地方法 start0, 該方法的聲明如下:
private native void start0();
1
Thread 類有個 registerNatives 本地方法,該方法主要的作用就是註冊一些本地方法供 Thread 類使用,如 start0(),stop0() 等等,可以說,所有操作本地線程的本地方法都是由它註冊的 . 這個方法放在一個 static 語句塊中,這就表明,當該類被載入到 JVM 中的時候,它就會被調用,進而註冊相應的本地方法。
private static native void registerNatives();
static{
registerNatives();
}
1
2
3
4
本地方法 registerNatives 是定義在 Thread.c 檔案中的。Thread.c 是個很小的檔案,定義了各個作業系統平台都要用到的關於線程的公用資料和操作,如代碼清單 1 所示。
清單1
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},
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
到此,可以容易的看出 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);
…
1
2
3
4
**這裡JVM_ENTRY是一個宏,用來定義**JVM_StartThread 函數,可以看到函數內建立了真正的平台相關的本地線程,其線程函數是 thread_entry,如清單 2 所示。
清單2
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);
}
1
2
3
4
5
6
7
8
9
可以看到調用了 vmSymbolHandles::run_method_name 方法,這是在 vmSymbols.hpp 用宏定義的:
class vmSymbolHandles: AllStatic {
…
template(run_method_name,"run")
至於 run_method_name 是如何聲明定義的,因為涉及到很繁瑣的代碼細節,本文不做贅述。感興趣的讀者可以自行查看 JVM 的原始碼。
圖. Java 線程建立呼叫歷程圖 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,如清單 2 所示。
清單2
static void thread_entry(JavaThread* thread, TRAPS) {
HandleMark hm(THREAD);
Handle obj(THREAD, thread->threadObj(www.feilinyule.cn));
JavaValue result(T_VOID);
JavaCalls::call_virtual(&result,obj,
KlassHandle(THREAD,SystemDictionary::Thread_klass()),
vmSymbolHandles::run_method_name(www.wangcai157.com),
vmSymbolHandles::void_method_signature(www.yibaoyule1.com),THREAD);
可以看到調用了 vmSymbolHandles::run_method_name 方法,這是在 vmSymbols.hpp 用宏定義的:
class vmSymbolHandles: AllStatic {
…
template(run_method_name,"run")
至於 run_method_name 是如何聲明定義的,因為涉及到很繁瑣的代碼細節,本文不做贅述。感興趣的讀者可以自行查看 JVM 的原始碼。
圖. Java 線程建立呼叫歷程圖
Java 建立線程的方法