首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 程式設計入門

Java Thread start() 與 run() 核心差異深度解析:為什麼直接呼叫 run() 不會開啟新執行緒?

     編輯:Java 程式設計入門

經典面試題:輸出結果是什麼?

public static void main(String[] args) {
    Thread t = new Thread() {
        public void run() {
            pong();
        }
    };
    t.run();  // 關鍵:這裡呼叫的是 run() 而非 start()
    System.out.println("ping");
}

static void pong() {
    System.out.println("pong");
}

輸出結果:

pong
ping

永遠是固定順序,不會 出現 ping pong 或交替輸出。

核心差異:一張表看懂

維度t.start()t.run()
是否建立新執行緒是,JVM 分配新執行緒、棧幀、程式計數器否,單純在當前執行緒(main)中同步呼叫方法
執行模式並發:新執行緒與 main 執行緒同時運行,搶佔 CPU串行:run() 結束前,main 執行緒卡住等待
run() 執行時機由執行緒調度器決定,非即時立即、同步執行
可重複呼叫否:第二次呼叫拋 IllegalThreadStateException是:只是普通方法,可重複呼叫
適用場景所有需並發執行的場景單元測試驗證 run() 邏輯、簡單同步任務

底層機制深度剖析

1. start() 內部做了什麼?

// Thread.java 簡化版
public synchronized void start() {
    if (threadStatus != 0) throw new IllegalThreadStateException();
    group.add(this);
    boolean started = false;
    try {
        start0(); // native 方法,向 OS 請求建立執行緒
        started = true;
    } finally {
        if (!started) group.threadStartFailed(this);
    }
}

private native void start0(); // JNI 呼叫 OS API (pthread_create / CreateThread)
  • JVM 向作業系統請求建立原生執行緒。
  • 新執行緒啟動後,自動呼叫 run() 方法。
  • 此時程式擁有兩條執行路徑:main 執行緒繼續往下、新執行緒執行 run()。

2. run() 只是普通方法

@FunctionalInterface
public interface Runnable {
    void run(); // 無任何特殊語義
}

public void run() {
    if (target != null) target.run(); // 若用 Runnable 建構,委派給 target
}

直接呼叫 t.run() 等同於 t.target.run(),完全在呼叫者執行緒棧幀中執行,零並發。

為什麼題目中偶爾會出現「ping pong」?

若改為 t.start(),兩執行緒並發,輸出順序取決於 CPU 調度:

  • main 執行緒可能先印 ping。
  • 新執行緒可能先印 pong。
  • 甚至交替(若 pong() 內有長運算或 Thread.yield())。

但題目中 System.out.println 有同步鎖,且指令極少,通常 main 執行緒搶先完成,輸出 pong ping(新執行緒啟動需幾百微秒,main 已印完)。

常見誤區與最佳實踐

誤區:「繼承 Thread 並重寫 run(),然後呼叫 run() 就能多執行緒。」

正確:必須呼叫 start()。若偏好實作 Runnable,用 new Thread(runnable).start()。

推薦寫法:Runnable + Lambda (Java 8+)

// 1. 獨立任務類(可復用、可測試)
class PingTask implements Runnable {
    @Override public void run() { System.out.println("pong"); }
}

// 2. 啟動
new Thread(new PingTask()).start();
// 或 Lambda
new Thread(() -> System.out.println("pong")).start();

避免繼承 Thread 的理由

  1. Java 單一繼承限制,繼承 Thread 就無法繼承業務基類。
  2. 任務與執行緒控制耦合,違反「關注點分離」。
  3. Runnable 可提交給 ExecutorService、CompletableFuture、ScheduledExecutorService 等高階 API。

除錯技巧:在 IDE 中觀察執行緒

  1. 設定中斷點於 run() 內部與 start() 後第一行。
  2. Debug 模式啟動,觀察 Debug 視窗 → Threads 面板。
  3. start():會出現 Thread-0、Thread-1 等新執行緒。
  4. run():只有 main 一條執行緒。

總結

  • start() = 啟動新執行緒(非同步、並發、由 OS 調度)。
  • run() = 任務邏輯入口(同步、串行、普通方法)。
  • 「新執行緒」只能透過 start()(或線程池)建立,絕不可直接呼叫 run() 期望並發。
  • 掌握此差異,是 Java 多執行緒編程的入門門檻,也是面試必考核心。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved