Java 建立線程的方法

來源:互聯網
上載者:User

標籤: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 建立線程的方法

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.