Java 實作多執行緒有兩條標準路徑:繼承 Thread 類別與實作 Runnable 介面。本文深度對比兩者語意差異、記憶體模型、資源共享機制、擴充性與最佳實務,並說明現代 Java 為何強烈建議使用 Runnable (或 Callable)。
| 維度 | 繼承 Thread | 實作 Runnable |
|---|---|---|
| 類別關係 | class MyThread extends Thread | class MyTask implements Runnable |
| 啟動方式 | new MyThread().start() | new Thread(new MyTask()).start() |
| 執行邏輯位置 | 覆寫 run() | 實作 run() |
| 繼承限制 | 單一繼承,無法繼承其他類 | 可繼承其他類,可實作多介面 |
| 資源共享 | 每個 Thread 實例獨擁資源 | 多 Thread 可共享同一 Runnable 實例 |
| 適用場景 | 簡單獨立任務、教學示範 | 資源共享、線程池、高併發、框架開發 |
class TicketSellerThread extends Thread {
private int tickets = 10; // 每個實例獨有 10 張票
@Override
public void run() {
for (int i = 0; i < 20; i++) {
if (tickets > 0) {
System.out.println(getName() + " 賣出第 " + tickets-- + " 張票");
}
}
}
}
public class ThreadExtendDemo {
public static void main(String[] args) {
// 三個獨立售票員,各賣 10 張,總共 30 張
new TicketSellerThread().start(); // Thread-0
new TicketSellerThread().start(); // Thread-1
new TicketSellerThread().start(); // Thread-2
}
}
輸出:三個執行緒各自從 10 賣到 1,總共賣出 30 張票。
class TicketSellerRunnable implements Runnable {
private int tickets = 10; // 共享同一個實例的 10 張票
@Override
public void run() {
for (int i = 0; i < 20; i++) {
// 注意:此處非執行緒安全,僅示範共享語意
if (tickets > 0) {
System.out.println(Thread.currentThread().getName() + " 賣出第 " + tickets-- + " 張票");
}
}
}
}
public class RunnableShareDemo {
public static void main(String[] args) {
TicketSellerRunnable sharedTask = new TicketSellerRunnable(); // 單一實例
// 三個執行緒共享同一任務實例,總共只賣 10 張票
new Thread(sharedTask, "窗口-A").start();
new Thread(sharedTask, "窗口-B").start();
new Thread(sharedTask, "窗口-C").start();
}
}
輸出:三個窗口共同賣 10 張票,總數為 10(但有競態條件,實際可能超賣或漏賣)。
// Runnable 共享資源的正確寫法:加同步
class SafeTicketSeller implements Runnable {
private int tickets = 10;
private final Object lock = new Object();
@Override
public void run() {
while (true) {
synchronized (lock) {
if (tickets <= 0) break;
System.out.println(Thread.currentThread().getName() + " 賣出第 " + tickets-- + " 張票");
}
// 模擬處理時間
try { Thread.sleep(10); } catch (InterruptedException ignored) {}
}
}
}
// 1. 定義任務 (Runnable/Callable)
class OrderProcessor implements Runnable {
private final Order order;
public OrderProcessor(Order order) { this.order = order; }
@Override public void run() { processOrder(order); }
}
// 2. 使用 ExecutorService 管理執行緒生命週期
ExecutorService executor = Executors.newFixedThreadPool(4); // 或自訂 ThreadPoolExecutor
// 3. 提交任務
executor.submit(new OrderProcessor(order1));
executor.submit(new OrderProcessor(order2));
// 4. 優雅關閉
executor.shutdown();
try {
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
executor.shutdownNow();
}
} catch (InterruptedException e) {
executor.shutdownNow();
Thread.currentThread().interrupt();
}
ExecutorService、CompletableFuture、Thread.ofVirtual() (Java 21) 皆接受 Runnable
class PriceFetcher implements Callable {
private final String productId;
public PriceFetcher(String productId) { this.productId = productId; }
@Override public Double call() throws Exception {
return externalApi.getPrice(productId); // 可能拋出異常
}
}
// 使用
ExecutorService executor = Executors.newFixedThreadPool(2);
Future future = executor.submit(new PriceFetcher("P123"));
try {
Double price = future.get(5, TimeUnit.SECONDS); // 阻塞等待結果,支援超時
System.out.println("價格: " + price);
} catch (TimeoutException e) {
future.cancel(true); // 取消執行
}
// 虛擬執行緒:輕量級、由 JVM 調度、適合高併發 I/O
Thread.startVirtualThread(() -> {
System.out.println("在虛擬執行緒中執行: " + Thread.currentThread());
});
// 或配合 ExecutorService
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < 10_000; i++) {
final int taskId = i;
executor.submit(() -> processTask(taskId));
}
} // 自動關閉並等待完成
實作 Runnable (或 Callable) 是 Java 多執行緒開發的標準範式。它將「任務邏輯」與「執行策略」解耦,支援資源共享、執行緒池複用、虛擬執行緒無縫遷移。繼承 Thread 僅適合極簡單的教學或一次性獨立任務。現代開發應拥抱 ExecutorService + Runnable/Callable,並準備遷移至虛擬執行緒以應對百萬級併發。