首頁  >  文章  >  Java  >  細品 Java 中啟動執行緒的正確和錯誤方式

細品 Java 中啟動執行緒的正確和錯誤方式

coldplay.xixi
coldplay.xixi轉載
2020-09-28 16:55:092057瀏覽

細品 Java 中啟動執行緒的正確和錯誤方式

細品Java 中啟動執行緒的正確與錯誤方式

前文回顧

  1. 詳細分析Java 中實作多執行緒的方法有幾種?(本質出發)

start 方法和run 方法的比較

程式碼示範:##

/**
 * <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();
    }
}复制代码

從上述範例可以分析出以下兩點:

  • 直接使用

    run 方法不會啟動一個新執行緒。 (錯誤方式)

  • start 方法會啟動一個新執行緒。 (正確方式)

start 方法分析

start 方法的意義以及注意事項

  • start 方法可以啟動一個新執行緒。

      線程物件在初始化之後調用了
    • start 方法之後, 當前線程(通常是主線程)會請求JVM 虛擬機器如果有空閒的話來啟動一下這邊的這個新線程。
    • 也就是說, 啟動一個新執行緒的本質就是請求 JVM 來運行這個執行緒。
    • 至於這個執行緒何時能夠運行,並不是簡單的由我們能夠決定的,而是由執行緒調度器去決定的。
    • 如果它很忙,即使我們運行了
    • start 方法,也不一定能夠立刻的啟動執行緒。
    • 所以說
    • srtart 方法呼叫之後,並不意味著這個方法已經開始運作了。它可能稍後才會運行,也很有可能很長時間都不會運行,比如說遇到了飢餓的情況。
    • 這也印證了有些情況下,線程1 先掉用了
    • start 方法,而線程2 後調用了start 方法,卻發現線程2 先執行線程1 後執行的情況。
    • 總結: 呼叫
    • start 方法的順序並不能決定真正執行緒執行的順序。
    • 注意事項
      • start 方法會牽扯到兩個執行緒。
      • 第一個就是主線程,因為我們必須要有一個主線程或是其他的線程(哪怕不是主執行緒)來執行這個
      • start 方法,第二個才是新的線程。
      • 很多情況下會忽略掉為我們創建線程的這個主線程,不要誤以為調用了
      • start 就已經是子線程去執行了,這個語句其實是主線程或者說是父線程來執行的,執行之後才去建立新線程。
  • start 方法建立新執行緒的準備工作

      首先,它會讓自己處於就緒狀態。
      • 就緒狀態指已經取得到除了 CPU 以外的其他資源, 如已經設定了上下文、堆疊、執行緒狀態以及 PC(PC 是一個暫存器,PC 指向程式運作的位置) 等。
    • 做完這些準備工作之後,就萬事俱備只欠東風了,東風就是 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,然後一旦執行緒執行完畢,執行緒就會變成終止狀態,而終止狀態永遠不可能再回回去,所以會拋出以上異常,也就是說不能回到初始狀態了。這裡描述的還不夠清晰,讓我們來看看原始碼能了解的更透徹。
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&#39;s list of threads
     * and the group&#39;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 &#39;not yet started&#39;
 */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 */
    }
}复制代码

run 方法分析

run 方法原始碼分析

@Overridepublic void run() {    // 传入了 target 对象(即 Runnable 接口的实现), 执行传入的 target 对象的 run 方法
    if (target != null) {
        target.run();
    }
}复制代码

對於run 方法的兩種情況

  • 第一種: 重寫了

    Thread 類別的run 方法,Threadrun 方法會失效, 將會執行重寫的run 方法。

  • 第二種: 傳入了target 物件(即Runnable 介面的實作),執行Thread 的原有run 方法接著接著執行target 物件的run 方法。

  • 總結:

    • run 方法就是一個普通的方法, 上文直接去執行run 方法也就是相當於我們執行自己寫的普通方法一樣,所以它的執行緒就是我們的主執行緒。
    • 所以要想真正的啟動線程,不能直接呼叫run 方法,而是要呼叫start 方法,可以間接的呼叫run 方法。

相關學習推薦:#java基礎

以上是細品 Java 中啟動執行緒的正確和錯誤方式的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:juejin.im。如有侵權,請聯絡admin@php.cn刪除