為什麼啟動執行緒是start方法?

為什麼啟動執行緒是start方法

 

十年可見春去秋來,百年可證生老病死,千年可嘆王朝更替,萬年可見斗轉星移。

             凡人如果用一天的視野,去窺探百萬年的天地,是否就如同井底之蛙?

 

背景:啟動執行緒是start() 還是run() 方法?相信這個問題很多人都知道是start(),但是如果我再問下去呢,為什麼是start()?你會如何作答呢?

 

一、理論課

當用start()開始一個執行緒後,執行緒就進入就緒狀態,使執行緒所代表的虛擬處理機處於可運行狀態,這意味著它可以由JVM調度並執行;但是這並不意味著執行緒就會立即運行,只有當cpu分配時間片時,這個執行緒獲得時間片時,才開始執行run()方法;start()方法去調用run(),而run()方法則是需要去重寫的,其包含的是執行緒的主體(真正的邏輯)。

二、執行緒六大狀態

Java 中,定義了 6 種執行緒狀態,NEWRUNNABLEBLOCKEDWAITINGTIMED_WAITING和TERMINATED

1 public enum State{
2    NEW,  
3    RUNNABLE,
4    BLOCKED,
5    WAITING,
6    TIMED_WAITING,
7    TERMINATED;     
8 }

6 種執行緒狀態關係圖

 

三、程式碼層次

楊總說的,一切問題歸咎於源碼!

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() 是啟動多執行緒的關鍵
             * 執行完成之後,新的執行緒已經在運行了
             * 這裡會創建一個新的執行緒,是一個 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 */
            }
        }
    }

 run方法源碼:

 @Override
    public void run() {
     // 簡單的運行,不會新起執行緒,target 是 Runnable
        if (target != null) {
            target.run();
        }
    }

從start()和run()方法的源碼可以看出,start方法的源碼也沒幾行程式碼,最主要的是 start0() 方法;run() 方法的源碼也比較簡單的,就是一個普通方法的調用;重點是這個 start0() 方法,她是真正實現多執行緒的關鍵。

start0方法源碼:

// native :就是本地方法 
private native void start0(); 

關於native方法簡述:

1、被native關鍵字修飾的方法叫做本地方法,本地方法和其它方法不一樣,本地方法意味著和平台有關,因此使用了native的程式可移植性都不太高;

2、native方法在JVM中運行時數據區也和其它方法不一樣,它有專門的本地方法棧;

3、native方法主要用於載入文件和動態鏈接庫,由於Java語言無法訪問作業系統底層資訊(比如:底層硬體設備等),這時候就需要藉助C語言來完成了;

4、被native修飾的方法可以被C語言重寫。

Java 跨平台圖

 

在start()中start0 被標記成 native ,也就是本地方法,並不需要我們去實現或者了解,只要了解下為什麼 start0() 會標記成 native ?

如上圖,start() 方法調用 start0() 方法後,該執行緒並不一定會立馬執行,只是將執行緒變成了可運行狀態(NEW —> RUNNABLE);具體什麼時候執行,取決於 CPU ,由 CPU 統一調度;我們又知道 Java 是跨平台的,可以在不同系統上運行,每個系統的 CPU 調度演算法不一樣,所以就需要做不同的處理,這件事情就只能交給 JVM 來實現了,start0() 方法自然就表標記成了 native。

 四、總結

Java 中實現真正的多執行緒是 start 中的 start0() 方法,run() 方法只是一個包含業務邏輯普通的方法;start是啟動多執行緒的唯一方式,其使得執行緒由創建態到就緒態,而這個執行緒是否被運行是由系統調度所決定的。

 

  十年可見春去秋來

    百年可證生老病死

      千年可嘆王朝更替

        萬年可見斗轉星移

          凡人如果用一天的視野

               去窺探百萬年的天地

                  是否就如同井底之蛙?