代碼演示:html
/** * <p> * start() 和 run() 的比較 * </p> * * @author 踏雪彡尋梅 * @version 1.0 * @date 2020/9/20 - 16:15 * @since JDK1.8 */ public class StartAndRunMethod { public static void main(String[] args) { // run 方法演示 // 輸出: name: main // 說明由主線程去執行的, 不符合新建一個線程的本意 Runnable runnable = () -> { System.out.println("name: " + Thread.currentThread().getName()); }; runnable.run(); // start 方法演示 // 輸出: name: Thread-0 // 說明新建了一個線程, 符合本意 new Thread(runnable).start(); } }
從以上示例能夠分析出如下兩點:java
直接使用 run
方法不會啓動一個新線程。(錯誤方式)多線程
start
方法會啓動一個新線程。(正確方式)ide
start
方法能夠啓動一個新線程。源碼分析
線程對象在初始化以後調用了 start
方法以後, 當前線程(一般是主線程)會請求 JVM 虛擬機若是有空閒的話來啓動一下這邊的這個新線程。this
也就是說, 啓動一個新線程的本質就是請求 JVM 來運行這個線程。操作系統
至於這個線程什麼時候可以運行,並非簡單的由咱們可以決定的,而是由線程調度器去決定的。線程
若是它很忙,即便咱們運行了 start
方法,也不必定可以馬上的啓動線程。code
因此說 srtart
方法調用以後,並不意味這個方法已經開始運行了。它可能稍後纔會運行,也頗有可能很長時間都不會運行,好比說遇到了飢餓的狀況。htm
這也就印證了有些狀況下,線程 1 先掉用了 start
方法,而線程 2 後調用了 start
方法,卻發現線程 2 先執行線程 1 後執行的狀況。
總結: 調用 start
方法的順序並不能決定真正線程執行的順序。
注意事項
start
方法會牽扯到兩個線程。
第一個就是主線程,由於咱們必需要有一個主線程或者是其餘的線程(哪怕不是主線程)來執行這個 start
方法,第二個纔是新的線程。
不少狀況下會忽略掉爲咱們建立線程的這個主線程,不要誤覺得調用了 start
就已是子線程去執行了,這個語句實際上是主線程或者說是父線程來執行的,被執行以後纔去建立新線程。
start
方法建立新線程的準備工做
首先,它會讓本身處於就緒狀態。
作完這些準備工做以後,就萬事俱備只欠東風了,東風就是 CPU 資源。
作完準備工做以後,線程才能被 JVM 或操做系統進一步去調度到執行狀態等待獲取 CPU 資源,而後纔會真正地進入到運行狀態執行 run
方法中的代碼。
須要注意: 不能重複的執行 start 方法
代碼示例
/** * <p> * 演示不能重複的執行 start 方法(兩次及以上), 不然會報錯 * </p> * * @author 踏雪彡尋梅 * @version 1.0 * @date 2020/9/20 - 16:47 * @since JDK1.8 */ public class CantStartTwice { public static void main(String[] args) { Runnable runnable = () -> { System.out.println("name: " + Thread.currentThread().getName()); }; Thread thread = new Thread(runnable); // 輸出: name: Thread-0 thread.start(); // 輸出: 拋出 java.lang.IllegalThreadStateException // 即非法線程狀態異常(線程狀態不符合規定) thread.start(); } }
報錯的緣由
start
一旦開始執行,線程狀態就從最開始的 New 狀態進入到後續的狀態,好比說 Runnable,而後一旦線程執行完畢,線程就會變成終止狀態,而終止狀態永遠不可能再返回回去,因此會拋出以上異常,也就是說不能回到初始狀態了。這裏描述的還不夠清晰,讓咱們來看看源碼能瞭解的更透徹。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 方法 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 */ } } }
第一步:
啓動新線程時會首先檢查線程狀態是否爲初始狀態, 這也是以上拋出異常的緣由。即如下代碼:
if (threadStatus != 0) throw new IllegalThreadStateException();
其中 threadStatus
這個變量的註釋以下,也就是說 Java 的線程狀態最初始(尚未啓動)的時候表示爲 0:
/* Java thread status for tools, * initialized to indicate thread 'not yet started' */ private volatile int threadStatus = 0;
第二步:
將其加入線程組。即如下代碼:
group.add(this);
第三步:
最後調用 start0()
這個 native 方法(native 表明它的代碼不是由 Java 實現的,而是由 C/C++ 實現的,具體實現能夠在 JDK 裏面看到,瞭解便可), 即如下代碼:
boolean started = false; try { // 第三步, 調用 start0 方法 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 */ } }
@Override public void run() { // 傳入了 target 對象(即 Runnable 接口的實現), 執行傳入的 target 對象的 run 方法 if (target != null) { target.run(); } }
第一種: 重寫了 Thread
類的 run
方法,Thread
的 run
方法會失效, 將會執行重寫的 run
方法。
第二種: 傳入了 target
對象(即 Runnable
接口的實現),執行 Thread
的原有 run
方法而後接着執行 target
對象的 run
方法。
總結:
run
方法就是一個普通的方法, 上文中直接去執行 run
方法也就是至關於咱們執行本身寫的普通方法同樣,因此它的執行線程就是咱們的主線程。
因此要想真正的啓動線程,不能直接調用 run
方法,而是要調用 start
方法,其中能夠間接的調用 run
方法。
若有寫的不足的,請見諒,請你們多多指教。