多執行緒是 Java 並發編程的基石。建立執行緒有兩種標準方式:繼承 Thread 類別與實作 Runnable 介面。本文深入比較兩者差異、執行機制、優缺點與最佳實踐。
java.lang.Threadrun() 方法 (執行緒執行體)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] 執行緒結束
java.lang.Runnablerun() 方法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 |
| 適用場景 | 簡單、獨立、不需複用的執行緒 | 複雜任務、需共享狀態、提交給執行緒池 |
// 錯誤示範:直接呼叫 run()
MyThread t = new MyThread();
t.run(); // 只是在主執行緒中同步執行 run() 方法,沒有建立新執行緒!
// 正確:呼叫 start()
t.start();
// 1. 向 JVM 註冊新執行緒
// 2. 配置呼叫棧、程式計數器等資源
// 3. 將執行緒置於「可執行」狀態
// 4. JVM 排程器稍後呼叫 run() 方法
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() 只是普通方法呼叫。
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();
}
}
}
IllegalThreadStateExceptionThread.setDefaultUncaughtExceptionHandlerThread 繼承與 Runnable 實作兩種基礎方式Callable + Future 獲取非同步結果核心原則:將「任務定義」(Runnable/Callable) 與「執行策略」(Thread/Executor) 分離,獲得最大彈性與可維護性。