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

Java 建立執行緒的兩種核心方式完整解析

     編輯:Java 相關知識

前言

多執行緒是 Java 並發編程的基石。建立執行緒有兩種標準方式:繼承 Thread 類別與實作 Runnable 介面。本文深入比較兩者差異、執行機制、優缺點與最佳實踐。

方式一:繼承 Thread 類別

實作步驟

  1. 建立類別繼承 java.lang.Thread
  2. 覆寫 run() 方法 (執行緒執行體)
  3. 建立實例並呼叫 start() 啟動

完整代碼範例

public class ThreadExtendDemo extends Thread {
    private final String threadName;
    private final int iterations;
    private final int sleepMs;

    public ThreadExtendDemo(String name, int iterations, int sleepMs) {
        this.threadName = name;
        this.iterations = iterations;
        this.sleepMs = sleepMs;
    }

    @Override
    public void run() {
        System.out.println("[" + threadName + "] 執行緒啟動");
        for (int i = 0; i < iterations; i++) {
            System.out.println("[" + threadName + "] 迴圈第 " + i + " 次");
            try {
                Thread.sleep(sleepMs);
            } catch (InterruptedException e) {
                System.out.println("[" + threadName + "] 被中斷");
                Thread.currentThread().interrupt(); // 重設中斷旗標
                return;
            }
        }
        System.out.println("[" + threadName + "] 執行緒結束");
    }

    public static void main(String[] args) {
        ThreadExtendDemo t1 = new ThreadExtendDemo("Thread-A", 5, 100);
        ThreadExtendDemo t2 = new ThreadExtendDemo("Thread-B", 5, 150);
        
        t1.start(); // 必須呼叫 start(),不能呼叫 run()
        t2.start();
        
        System.out.println("[Main] 主執行緒繼續執行...");
    }
}

執行結果範例 (每次不同,體現並發非確定性)

[Main] 主執行緒繼續執行...
[Thread-A] 執行緒啟動
[Thread-B] 執行緒啟動
[Thread-A] 迴圈第 0 次
[Thread-B] 迴圈第 0 次
[Thread-A] 迴圈第 1 次
[Thread-B] 迴圈第 1 次
...
[Thread-A] 執行緒結束
[Thread-B] 執行緒結束

方式二:實作 Runnable 介面

實作步驟

  1. 建立類別實作 java.lang.Runnable
  2. 實作 run() 方法
  3. 將實例傳給 Thread 建構子,再呼叫 start()

完整代碼範例

public class RunnableImplDemo implements Runnable {
    private final String taskName;
    private final int count;
    private final int delayMs;

    public RunnableImplDemo(String name, int count, int delayMs) {
        this.taskName = name;
        this.count = count;
        this.delayMs = delayMs;
    }

    @Override
    public void run() {
        System.out.println("[" + taskName + "] 任務開始");
        for (int i = 0; i < count; i++) {
            System.out.println("[" + taskName + "] 執行步驟 " + i);
            try {
                Thread.sleep(delayMs);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                return;
            }
        }
        System.out.println("[" + taskName + "] 任務完成");
    }

    public static void main(String[] args) {
        RunnableImplDemo task1 = new RunnableImplDemo("Task-1", 3, 200);
        RunnableImplDemo task2 = new RunnableImplDemo("Task-2", 3, 300);
        
        // 必須包裝在 Thread 中才能啟動
        new Thread(task1, "Worker-1").start();
        new Thread(task2, "Worker-2").start();
        
        // 同一個 Runnable 可被多個 Thread 共用 (共享狀態)
        RunnableImplDemo sharedTask = new RunnableImplDemo("Shared", 2, 100);
        new Thread(sharedTask, "Thread-X").start();
        new Thread(sharedTask, "Thread-Y").start();
    }
}

兩種方式深度比較

比較項目繼承 Thread實作 Runnable
語法關係is-a (繼承關係)has-a (委派關係)
單一繼承限制受限:無法再繼承其他類別不受限:可繼承其他類別、實作多介面
程式碼重用較難 (邏輯綁定在 Thread 子類)容易 (任務邏輯獨立,可給多執行緒/執行緒池使用)
取得執行緒名稱this.getName() 或 getName()Thread.currentThread().getName()
傳遞參數建構子傳遞建構子傳遞 (更靈活)
共享資料需額外設計 (static 或外部物件)天然支援:同一 Runnable 實例給多 Thread
適用場景簡單、獨立、不需複用的執行緒複雜任務、需共享狀態、提交給執行緒池

關鍵概念:start() vs run()

// 錯誤示範:直接呼叫 run()
MyThread t = new MyThread();
t.run(); // 只是在主執行緒中同步執行 run() 方法,沒有建立新執行緒!

// 正確:呼叫 start()
t.start(); 
// 1. 向 JVM 註冊新執行緒
// 2. 配置呼叫棧、程式計數器等資源
// 3. 將執行緒置於「可執行」狀態
// 4. JVM 排程器稍後呼叫 run() 方法

start() 內部原理 (JDK 源碼節錄)

public synchronized void start() {
    if (threadStatus != 0) throw new IllegalThreadStateException();
    group.add(this); // 加入執行緒群組
    boolean started = false;
    try {
        start0(); // native 方法,由 JVM 實作
        started = true;
    } finally {
        if (!started) group.threadStartFailed(this);
    }
}

private native void start0(); // 本地方法,跨平台由 JVM 實作

核心差異:start() 建立新執行緒 (OS 層面),run() 只是普通方法呼叫。

進階:第三種方式 - 實作 Callable + FutureTask (有回傳值)

適用場景

  • 任務執行完需取得回傳值
  • 任務可能拋出受檢異常
  • 需支援取消、超時控制

完整範例

import java.util.concurrent.*;

public class CallableDemo implements Callable<String> {
    private final int taskId;

    public CallableDemo(int id) { this.taskId = id; }

    @Override
    public String call() throws Exception {
        System.out.println("Task-" + taskId + " 開始計算...");
        Thread.sleep(1000);
        int result = taskId * 100;
        return "Task-" + taskId + " 結果: " + result;
    }

    public static void main(String[] args) throws Exception {
        // 建立 Callable 任務
        CallableDemo task1 = new CallableDemo(1);
        CallableDemo task2 = new CallableDemo(2);
        
        // 包裝為 FutureTask (實作 RunnableFuture → Runnable)
        FutureTask<String> future1 = new FutureTask<>(task1);
        FutureTask<String> future2 = new FutureTask<>(task2);
        
        // 提交給執行緒執行
        new Thread(future1, "Callable-Thread-1").start();
        new Thread(future2, "Callable-Thread-2").start();
        
        // 主執行緒阻塞等待結果 (get() 會阻塞直到完成)
        System.out.println("主執行緒等待結果...");
        String r1 = future1.get(); // 可設定超時: get(5, TimeUnit.SECONDS)
        String r2 = future2.get();
        
        System.out.println("收到結果1: " + r1);
        System.out.println("收到結果2: " + r2);
    }
}

現代最佳實踐:執行緒池

不要手動 new Thread(),生產環境請使用 ExecutorService:

import java.util.concurrent.*;

public class ThreadPoolBestPractice {
    public static void main(String[] args) {
        // 固定大小執行緒池
        ExecutorService executor = Executors.newFixedThreadPool(4);
        
        // 提交 Runnable (無回傳值)
        executor.submit(() -> {
            System.out.println("Runnable 任務執行中...");
        });
        
        // 提交 Callable (有回傳值)
        Future<String> future = executor.submit(() -> {
            Thread.sleep(500);
            return "Callable 結果";
        });
        
        try {
            System.out.println("結果: " + future.get());
        } catch (Exception e) { e.printStackTrace(); }
        
        // 關閉執行緒池 (重要!否則 JVM 不會退出)
        executor.shutdown();
        try {
            if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
                executor.shutdownNow();
            }
        } catch (InterruptedException e) {
            executor.shutdownNow();
        }
    }
}

常見錯誤與避坑指南

  1. 直接呼叫 run():以為啟動執行緒,實則同步執行
  2. 重複呼叫 start():同一 Thread 實例只能 start() 一次,否則拋 IllegalThreadStateException
  3. 忽略 InterruptedException:捕獲後未重設中斷旗標,導致上層無法感知中斷
  4. 共享可變狀態無同步:多執行緒存取非執行緒安全集合/變數導致資料競爭
  5. 未關閉執行緒池:主程式結束但 JVM 存活,資源洩漏
  6. 在執行緒中拋出未處理異常:會導致執行緒悄悄死亡,建議統一用 Thread.setDefaultUncaughtExceptionHandler

總結

  • 入門階段:理解 Thread 繼承與 Runnable 實作兩種基礎方式
  • 進階階段:掌握 Callable + Future 獲取非同步結果
  • 實務階段:棄用手動 new Thread(),全面改用執行緒池 管理生命週期、控制併發數、重用執行緒
  • 架構階段:引入 CompletableFuture、Reactive Streams、Virtual Threads (Java 21+) 進行非同步編排

核心原則:將「任務定義」(Runnable/Callable) 與「執行策略」(Thread/Executor) 分離,獲得最大彈性與可維護性。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved