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

Java 多執行緒實作兩大路徑:Thread 繼承與 Runnable 介面完整對比

     編輯:Java 相關知識

前言

Java 實作多執行緒有兩條標準路徑:繼承 Thread 類別與實作 Runnable 介面。本文深度對比兩者語意差異、記憶體模型、資源共享機制、擴充性與最佳實務,並說明現代 Java 為何強烈建議使用 Runnable (或 Callable)。

核心定義對比

維度繼承 Thread實作 Runnable
類別關係class MyThread extends Threadclass MyTask implements Runnable
啟動方式new MyThread().start()new Thread(new MyTask()).start()
執行邏輯位置覆寫 run()實作 run()
繼承限制單一繼承,無法繼承其他類可繼承其他類,可實作多介面
資源共享每個 Thread 實例獨擁資源多 Thread 可共享同一 Runnable 實例
適用場景簡單獨立任務、教學示範資源共享、線程池、高併發、框架開發

範例一:繼承 Thread - 各自獨立資源


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 張票。

範例二:實作 Runnable - 共享資源


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) {}
        }
    }
}

現代 Java 最佳實務:Runnable + 執行緒池


// 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();
}

為什麼不建議直接繼承 Thread?

  1. 違反單一職責:Thread 負責「執行緒控制」,任務邏輯應分離
  2. 無法複用:任務邏輯綁定在 Thread 子類,難以在執行緒池、定時器、CompletableFuture 中複用
  3. 繼承限制:Java 單一繼承,繼承 Thread 就無法繼承業務基類
  4. 資源共享困難:需額外傳遞共享物件,不如 Runnable 自然
  5. 現代 API 設計:ExecutorService、CompletableFuture、Thread.ofVirtual() (Java 21) 皆接受 Runnable

進階:Callable + Future 獲取回傳值


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); // 取消執行
}

虛擬執行緒時代 (Java 21+)


// 虛擬執行緒:輕量級、由 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,並準備遷移至虛擬執行緒以應對百萬級併發。

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