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

Java 多線程核心:Runnable 與 Thread 差異深度解析與最佳實踐

     編輯:Java 相關知識

前言

Java 多線程開發有兩條基礎路徑:繼承 Thread 類與實現 Runnable 介面。雙方最終都靠 Thread.start() 啟動新線程,但設計哲學、擴展性與資源共享模式截然不同。本文深度剖析兩者差異、底層原理、資源共享陷阱,並給出生產級選型建議。

路徑一:繼承 Thread 類

class MyThread extends Thread {
    private final String name;
    public MyThread(String name) { this.name = name; }
    
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("Thread: " + name + ", i=" + i);
        }
    }
}

// 使用
MyThread t1 = new MyThread("A");
MyThread t2 = new MyThread("B");
// ❌ 錯誤:直接呼叫 run(),在主線程順序執行
t1.run(); t2.run(); 
// ✅ 正確:呼叫 start(),JVM 分配新線程執行 run()
t1.start(); t2.start();

關鍵點

  • run() 只是普通方法,直接呼叫不會開新線程。
  • start() 是 native 方法,請求 JVM 創建新線程並調度執行 run()。
  • 單繼承限制:已繼承 Thread,無法繼承其他業務類。

路徑二:實現 Runnable 介面(推薦)

class MyTask implements Runnable {
    private final String name;
    public MyTask(String name) { this.name = name; }
    
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("Runnable: " + name + ", i=" + i);
        }
    }
}

// 使用:同一 Task 實例可被多個 Thread 執行 → 實現資源共享
MyTask task = new MyTask("Shared");
new Thread(task).start();
new Thread(task).start();
new Thread(task).start();

Runnable 優勢

  1. 解耦任務與執行:任務邏輯獨立於線程控制。
  2. 支援多實現:可同時實現多個介面,或繼承類並實現 Runnable。
  3. 天然資源共享:同一 Runnable 實例被多線程執行,成員變量自動共享。
  4. 適配線程池:ExecutorService.execute(Runnable) 只接受 Runnable/Callable。

核心差異對比表

維度extends Threadimplements Runnable
繼承限制單繼承,無法繼承其他類無限制,可繼承類+實現多介面
任務與線程耦合度強耦合(類即線程)解耦(任務獨立,線程只是載體)
資源共享困難:每 new 一個 Thread 即新實例,需額外機制共享自然:同一 Runnable 實例傳給多 Thread
線程池適配不直接支援原生支援
訪問當前線程this.getName() 等Thread.currentThread()
適用場景簡單獨立線程、教學演示生產環境、複雜任務、線程池、資源共享

資源共享經典案例:賣票問題

Thread 版:每線程各賣各的(錯誤)

class TicketThread extends Thread {
    private int tickets = 10; // 每個實例獨擁 10 張
    public void run() {
        for (int i = 0; i < 20; i++) {
            if (tickets > 0) System.out.println("售出: " + tickets--);
        }
    }
}

public static void main(String[] args) {
    new TicketThread().start(); // 賣 10 張
    new TicketThread().start(); // 另賣 10 張
    new TicketThread().start(); // 又賣 10 張
    // 總共賣 30 張!實際只有 10 張。
}

Runnable 版:共享票池(正確邏輯,但有線程安全問題)

class TicketTask implements Runnable {
    private int tickets = 10; // 同一實例,共享 tickets
    public void run() {
        for (int i = 0; i < 20; i++) {
            if (tickets > 0) System.out.println("售出: " + tickets--);
        }
    }
}

public static void main(String[] args) {
    TicketTask task = new TicketTask();
    new Thread(task).start();
    new Thread(task).start();
    new Thread(task).start();
    // 總共賣 10 張,邏輯正確!
    // 但 tickets-- 非原子,高併發下會超賣/重賣 → 需同步
}

線程安全修正版

class SafeTicketTask implements Runnable {
    private int tickets = 10;
    private final Object lock = new Object();
    
    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) {}
        }
    }
}

Thread 與 Runnable 關係本質

public class Thread implements Runnable {
    private Runnable target;
    
    public Thread(Runnable target) { this.target = target; }
    
    @Override
    public void run() {
        if (target != null) target.run();
    }
    
    private native void start0(); // JNI 呼叫 OS 創建線程
}

結論:Thread 本身就是 Runnable 的實現類。Thread(Runnable target) 是組合模式,將任務委託給外部 Runnable。

進階:Callable + Future(有返回值、可拋異常)

Callable task = () -> {
    Thread.sleep(1000);
    return 42;
};

ExecutorService es = Executors.newSingleThreadExecutor();
Future future = es.submit(task);
System.out.println("結果: " + future.get()); // 阻塞獲取結果
es.shutdown();

生產環境最佳實踐

1. 統一使用 Runnable/Callable + 線程池

// 避免手動 new Thread()
ExecutorService executor = Executors.newFixedThreadPool(10);
// 或自定義 ThreadPoolExecutor 精確控制核心/最大/隊列/拒絕策略

executor.execute(() -> { /* Runnable 任務 */ });
executor.submit(() -> { return "result"; }); // Callable

2. 線程命名便於排查

new Thread(task, "Order-Processor-1").start();
// 或線程池
new ThreadFactoryBuilder().setNameFormat("Order-Processor-%d").build();

3. 避免繼承 Thread 的反模式

// ❌ 反模式:為了覆寫 run() 而繼承 Thread
class MyThread extends Thread { ... }

// ✅ 正規:實現 Runnable,組合 Thread
class MyTask implements Runnable { ... }
new Thread(new MyTask(), "MyTask").start();

4. 共享數據顯式同步或用並發容器

// 共享計數器
AtomicInteger count = new AtomicInteger();
// 共享 Map
ConcurrentHashMap cache = new ConcurrentHashMap<>();
// 共享隊列
BlockingQueue queue = new LinkedBlockingQueue<>();

總結

  • Thread:線程控制類,核心能力是 start() 請求 OS 創建線程。
  • Runnable:任務載體,單一方法 run(),無線程管理能力。
  • 本質關係:Thread 組合 Runnable(策略模式)。
  • 選型鐵律:
    1. 業務邏輯 必須實現 Runnable/Callable。
    2. 執行控制 交給線程池(ExecutorService)。
    3. 共享狀態 使用並發包工具(Atomic*, Concurrent*, Lock)。
    4. 極少情況繼承 Thread(僅教學、極簡獨立線程)。

理解這層關係,是寫出高併發、可維護 Java 多線程程式的基石。

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