Java Thread源碼解析

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.結語

學如逆水行舟,不進則退。

4.參考博客

Java線程的創建
Java Thread:揭開Run方法被調用的真正面紗

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章