Java Thread源碼解析
1.前言
我們知道,new一個thread,調用它的start的方法,就可以創建一個線程,並且啓動該線程,然後執行該線程需要執行的業務邏輯,那麼run方法是怎麼被執行的呢?
2.正文
2.1.Thread源碼解析
先看如下例子。
@Test
public void test03() throws InterruptedException {
Thread thread = new Thread(new MyRunnable());
thread.setDaemon(true);
thread.start();
for (int i = 0; i < 10000; i++) {
System.out.println("-----------");
}
thread.join();
}
public static
class MyRunnable implements Runnable {
private long currentTime = System.currentTimeMillis();
@Override
public void run() {
try {
Thread.sleep(2000);
System.out.println(System.currentTimeMillis() - currentTime);
hh();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void hh() {
System.out.println("hh=========");
}
}
實現線程三種方式(Runnable,Callable,Thread),無論哪種方式,都會先創建線程Thread對象再調用start()方法。需要注意的是有一個靜態代碼塊。
static {
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方法名,第二個的括號裏面爲參數類型,括號外面爲返回值,第三個代表着c/c++對應的方法。有興趣的可以去了解JNI,JNI PDF著名書籍下載,這本書還是不錯的,需要有點英文功底的,但是閱讀後對你理解native方法源碼有更大幫助。在Thread類中,再看其構造函數。
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
我只選擇了一個構造函數,可以看見構造函數都調用了init()方法。
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name;
Thread parent = currentThread();
SecurityManager security = System.getSecurityManager();
if (g == null) {
//安全檢查
if (security != null) {
g = security.getThreadGroup();
}
//設置線程組
if (g == null) {
g = parent.getThreadGroup();
}
}
//檢查可達性
g.checkAccess();
//是否有權限訪問
if (security != null) {
if (isCCLOverridden(getClass())) {
security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
}
}
//往線程組添加線程但未啓動
g.addUnstarted();
this.group = g;
this.daemon = parent.isDaemon();
this.priority = parent.getPriority();
if (security == null || isCCLOverridden(parent.getClass()))
this.contextClassLoader = parent.getContextClassLoader();
else
this.contextClassLoader = parent.contextClassLoader;
this.inheritedAccessControlContext =
acc != null ? acc : AccessController.getContext();
this.target = target;
setPriority(priority);
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
/* Stash the specified stack size in case the VM cares */
this.stackSize = stackSize;
/* Set thread ID */
tid = nextThreadID();
}
上述代碼,對現在進行了一系列的初始化,我們可以看個大致初始化過程,設置線程組、優先級、是否是守護線程、線程上下文加載器、target就是目標對象即實現了線程的那三種方法所構造的對象,從父線程創建ThreadLocal、設置線程ID等操作。初始化完成後,就該調用start()方法,讓其進入準備狀態。讓我們來開start()方法。
public synchronized void start() {
//如果不是準備狀態,拋出異常,即一個線程只能使用一次start()方法
if (threadStatus != 0)
throw new IllegalThreadStateException();
//添加進線程組
group.add(this);
//開始標記
boolean started = false;
try {
//調用native方法
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 */
}
}
}
private native void start0();
上述代碼,除開native方法,配上註釋很容易理解了。但是我們一直沒看見在哪裏調用run方法,只有start0()方法,而在本文最開始,我們發現
{"start0", "()V",(void *)&JVM_StartThread}
這一句話代表這個方法將會被Java調用執行,在 jvm.cpp 中。
JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))
JavaThread *native_thread = NULL;
...
native_thread = new JavaThread(&thread_entry, sz);
...
Thread::start(native_thread);
在hotspot\src\share\vm\runtime\thread.cpp中查看JavaThread的實現:
JavaThread::JavaThread(ThreadFunction entry_point, size_t stack_sz) : ...
{
...
os::create_thread(this, thr_type, stack_sz);
}
void Thread::start(Thread* thread)
{
...
os::start_thread(thread);
}
在hotspot\src\os目錄下可以看到windows, linux, solaris和posix的實現,先檢查linux\vm\os_linux.cpp:
bool os::create_thread(Thread* thread, ThreadType thr_type, size_t stack_size)
{
...
int ret = pthread_create(&tid, &attr, (void* (*)(void*)) java_start, thread);
...
}
通過調用平臺的API創建一個線程!在回到 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”!
...
}
就是這裏進行調用了Thread類中的run方法。而Thread類的run方法如下:
public void run() {
if (target != null) {
target.run();
}
}
很明顯,該方法又去調用target對象的run方法。至此我們理解了一個完整的Thread的創建以及運行過程。後面我將貼出比較重要的一些方法,有興趣可以閱讀源碼。
//返回當前正在運行的線程
public static native Thread currentThread();
//向調度程序發出的提示,表明當前線程願意使用當前的處理器。調度程序可以忽略這個提示。
public static native void yield();
//使當前正在執行的線程以指定的毫秒數暫停(暫時停止執行),具體取決於系統定時器和調度程序的精度和準確性。
public static native void sleep(long millis) throws InterruptedException;
3.結語
學如逆水行舟,不進則退。