## 導語
我們知道,new一個thread,調用它的start的方法,就可以創建一個線程,并且啟動該線程,然后執行該線程需要執行的業務邏輯,
那么run方法是怎么被執行的呢?
## os線程
java的一個線程實際上是對應了操作系統的一個線程;而操作系統實現線程有三種方式:
* 內核線程實現
* 用戶線程實現
* 用戶線程加輕量級進程混合實現
**內核線程實現**

**簡要說明**:
內核線程(Kernel-Level Thread簡稱KLT)是直接由操作系統內核直接支持的線程,這種線程由內核完成線程切換,內核通過操縱調度器對線程進行調度,
并負責將線程的任務映射到各個處理器上。
每個內核線程可以視為內核的一個分身,這樣操作系統就有能力處理多件事情,這種支持多線程的內核就叫做多線程內核;
程序一般不會直接操作內核線程,而是去使用內核線程的一種高級接口:輕量級進程(Light Weight Process,簡稱LWP),輕量級進程就是我們通常所講的
線程,每個輕量級進程都由一個內核線程支持,這種輕量級進程與內核線程1:1的關系稱為一對一的線程模型。
**優勢**:
由于有了內核線程的支持,每個輕量級進程都稱為一個獨立的調度單元,即使有一個輕量級進程在系統中阻塞了,也不會影響整個進程繼續工作;
**劣勢**:
由于基于內核線程實現,所以各種線程操作(創建,析構及同步等)都需要進行系統調用(系統調用代價相對較高,需要在用戶態`[User Mode]`和內核態`[Kernel Mode]`來回切換);
每個輕量級進程需要一個內核線程支持,因此需要消耗一定的內核資源(如內核線程的棧空間),因此一個系統支持輕量級進程的數量是有限的;
**用戶線程實現**

**簡要說明**:
**用戶線程**:
* 廣義上來講,任何非內核線程都可以認為是用戶線程(User Thread,簡稱UT);
* 狹義上來講,完全建立在用戶空間的線程庫上,系統內核不能感知線程存在的實現;
**特點**:
* 用戶線程的創建、同步、銷毀和調度完全在用戶態中完成,不需要內核的幫助,這種線程不需要切換到內核態,操作可以非常快速且低消耗;
* 支持更大的線程數量;
* 這種進程與用戶線程之間1:N的關系稱為一對多的線程模型;
* 用戶線程的優勢就是不需要系統內核支援,劣勢就是沒有內核支持,所有的線程操作(如線程的創建、切換和調度等)都需要用戶程序自己處理。
**用戶線程加輕量級進程混合實現**

**簡要說明**:
**用戶線程+輕量級進程特點**:
* 用戶線程的創建、析構、切換等操作很廉價;
* 支持大規模的用戶線程并發;
* 操作系統提供的輕量級進程作為用戶線程和內核線程之間的橋梁,提供線程調度功能及處理器映射;
* 用戶線程的系統調用通過輕量級進程完成,降低了整個進程完全被阻塞的風險;
* 這種用戶線程和輕量級進程的數量比不定,即N:M的關系,稱為多對多的線程模型
## Java線程
Java線程在JDK1.2之前,是基于用戶線程實現的。而在JDK1.2中,線程模型替換為基于操作系統原生線程模型來實現。
而在目前的JDK版本中,操作系統支持怎樣的線程模型,在很大程度上決定了Java虛擬機的線程是怎樣映射的,這點在不同的平臺上沒法達成一致。
對于Sun JDK來說,它的Windows版本和Linux版本都是使用一對一的線程模型實現的,一條Java線程映射到一條輕量級進程之中。
### Java線程創建
**創建方式**
~~~
public class TestThread {
class ThreadExtend extends Thread {
@Override
public void run() {
// TODO: 2018/9/6
}
}
class RunnableExtend implements Runnable {
@Override
public void run() {
// TODO: 2018/9/6
}
}
private void testThreadPortal() {
Thread thread1 = new ThreadExtend();
thread1.start();
Thread thread2 = new Thread(new RunnableExtend());
thread2.start();
}
}
~~~
**Desc**:我們看到,無論以哪種方式創建,最終我們都會重寫一個叫做 run 的方法,來處理我們的業務邏輯,然而我們都是調用一個start方法,來啟動一個線程;
那 start方法和run方法之間是一個什么關系呢?從后邊的介紹我們將獲得這樣一個信息:run就是一個回調函數,和我們普通的函數沒有區別。
**Java線程的實現**
一個 Java 線程的創建本質上就對應了一個本地線程(native thread)的創建,兩者是一一對應的。
關鍵問題是:本地線程執行的應該是本地代碼,而 Java 線程提供的線程函數(run)是 Java 方法,編譯出的是 Java 字節碼,
所以, Java 線程其實提供了一個統一的線程函數,該線程函數通過 Java 虛擬機調用 Java 線程方法 , 這是通過 Java 本地方法調用來實現的。
以下是 Thread#start 方法的示例:
~~~
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.
*
* A zero status value corresponds to state "NEW".
*/
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. */
group.add(this);
boolean started = false;
try {
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, 而start0聲明如下:
`private native void start0();`
也就是新創建的線程啟動調用native start0方法,而這些native方法的注冊是在Thread對象初始化的時候完成的,look:
~~~
public
class Thread implements Runnable {
/* Make sure registerNatives is the first thing <clinit> does. */
private static native void registerNatives();
static {
registerNatives();
}
//......
}
~~~
Thread 類有個 registerNatives 本地方法,該方法主要的作用就是注冊一些本地方法供 Thread 類使用,如 start0(),stop0() 等等,可以說,所有操作本地線程的本地方法都是由它注冊的。
這個方法放在一個 static 語句塊中,當該類被加載到 JVM 中的時候,它就會被調用,進而注冊相應的本地方法。
而本地方法 registerNatives 是定義在 Thread.c 文件中的。Thread.c 是個很小的文件,它定義了各個操作系統平臺都要用到的關于線程的公用數據和操作,如下:
~~~
JNIEXPORT void JNICALL
Java_Java_lang_Thread_registerNatives (JNIEnv *env, jclass cls){ //registerNatives
(*env)->RegisterNatives(env, cls, methods, ARRAY_LENGTH(methods));
}
static JNINativeMethod methods[] = {
{"start0", "()V",(void *)&JVM_StartThread}, //start0 方法
{"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->start0 的方法,實際上會調用到 JVM_StartThread 方法,那這個方法又是怎么處理的呢?
實際上,我們需要看到的是該方法最終要調用 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(), //LOOK! 看這里
vmSymbolHandles::void_method_signature(),THREAD);
}
~~~
可以看到調用了 vmSymbolHandles::run_method_name 方法,而run_method_name是在 vmSymbols.hpp 用宏定義的:
~~~
class vmSymbolHandles: AllStatic {
...
template(run_method_name,"run") //LOOK!!! 這里決定了調用的方法名稱是 “run”!
...
}
~~~
## Java線程創建調用關系
