1. 程式人生 > >Java 創建線程的方法

Java 創建線程的方法

概念 gist thread jobject 關系 stat value pthread reg

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 創建線程的方法