用代碼說話:如何正確啓動線程

先來看下結論:正確啓動線程的方式是使用start()方法,而不是使用run()方法。html

代碼實戰

1. 輸出線程名稱

「Talk is cheap. Show me the code」,用代碼說話:分別調用run()方法和start()方法,打印輸出線程的名字。安全

public class StartAndRunThread {
    public static void main(String[] args) {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        };
        runnable.run();
        new Thread(runnable).start();
    }
}

運行結果:
image.png併發

2. 深刻一點

若是代碼是這樣的,執行結果有什麼不一樣呢?ide

public class StartAndRunThread {
    public static void main(String[] args) {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        };
        runnable.run();
        new Thread(runnable).start();
        runnable.run();
    }
}

執行結果爲:
image.png源碼分析

是否是有點意外?然而,這就是真相。其實也不難解釋。this

  1. 咱們說的併發是什麼,併發不就是線程之間的運行互不干擾嘛?當JVM啓動的時候,建立一個mian線程來運行main()方法。當執行到「new Thread(runnable).start();」的時候main線程會新建一個Thread-0線程。main線程和Thread-0線程的執行時互不相干的,因此可能不會出現「main-Thread-0-main」的結果。操作系統

  2. 我執行了n(n>20)次,運行結果依然如上圖所示,沒有出現「main-Thread-0-main」。這是爲何呢?回憶一下線程的生命週期, Java中,線程(Thread)定義了6種狀態: NEW(新建)、RUNNABLE(可執行)、BLOCKED(阻塞)、WAITING(等待)、TIMED_WAITING(限時等待)、TERMINATED(結束)。當調用了start()方法以後,線程進入RUNNABLE狀態,RUNNABLE的意思是可運行,便可能正在執行,也可能沒有正在執行。那調用了start方法以後,何時執行呢?調用start()方法以後,咱們只是告訴JVM去執行這個線程,至於何時運行是由線程調度器來決定的。從操做系統層面,其實調用start()方法以後要去獲取操做系統的時間片,獲取到纔會執行。這個問題,能夠對比思考「 thread.start()調用以後線程會馬上執行嗎?」更多能夠參考:從源碼解讀線程(Thread)和線程池(ThreadPoolExecutor)的狀態線程

start()方法源碼分析

start()源碼以下:rest

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 */
        }
    }
}

能夠看到,start()方法被synchronized關鍵字修飾,保證了線程安全。啓動流程分爲下面三個步驟:code

  1. 首先會檢查線程狀態,只有threadStatus == 0(也就是線程處於NEW狀態)狀態下的線程才能繼續,不然會拋出IllegalThreadStateException。

  2. 將線程加入線程組

  3. 調用native方法——start0()方法啓動線程。

線程啓動相關問題

1. 一個線程兩次調用start()方法會出現什麼狀況?

會拋出IllegalThreadStateException,具體緣由能夠用源碼和線程啓動步驟進行說明。

2. 既然 start() 方法會調用 run() 方法,爲何咱們選擇調用 start() 方法,而不是直接調用 run() 方法呢?

start()纔是真正啓動一個線程,而若是直接調用run(),那麼run()只是一個普通的方法而已,和線程的生命週期沒有任何關係。用代碼驗證一下:

public class Main implements Runnable {

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        new Main().run();
        new Thread(new Main()).start();
    }

}

image.png

在上面代碼中,直接調用run()方法,run()只是一個普通的方法,由當前線程——main線程執行。start()纔是真正啓動一個線程——Thread0,run()方法由線程Thread0執行。

3. 上面說start()會調用run()方法,這個怎麼證實?爲何在start()方法的源碼中沒有看到調用了run()方法?

能夠看start()方法的註釋部分:

/**
* Causes this thread to begin execution; the Java Virtual Machine
* calls the <code>run</code> method of this thread.
* <p>
* The result is that two threads are running concurrently: the
* current thread (which returns from the call to the
* <code>start</code> method) and the other thread (which executes its
* <code>run</code> method).
* <p>
* It is never legal to start a thread more than once.
* In particular, a thread may not be restarted once it has completed
* execution.
*
* @exception  IllegalThreadStateException  if the thread was already
*               started.
* @see        #run()
* @see        #stop()
*/

也就是說當該線程開始執行的時候,Java虛擬機會自動調用該線程的run()方法。

相關文章
相關標籤/搜索