在 Java 多線程開發中,Runnable 接口與 Thread 類是兩種創建線程的基本方式,但它們在線程安全、代碼復用、架構設計上有本質區別。本文通過經典的「售票案例」深度剖析兩者差異。
| 維度 | Runnable 接口 | Thread 類 |
|---|---|---|
| 本質 | 函數式接口,僅定義 run() 方法 | 類,實現了 Runnable,代表線程實體 |
| 創建線程 | 實現接口 → 實例化對象 → 傳入 Thread 構造器 → start() | 繼承 Thread → 重寫 run() → 實例化 → start() |
| 數據共享 | 天然支持:同一 Runnable 實例可被多個 Thread 共享 | 數據隔離:每個 Thread 子類實例擁有獨立數據副本 |
| 繼承限制 | 無限制,可繼承其他類 | 單繼承限制,已繼承 Thread 無法繼承其他業務類 |
| 線程安全 | 共享數據需自行同步(synchronized / Lock) | 各實例數據獨立,無共享安全問題,但無法實現真正的「多線程賣同一張票」 |
public class MyThread extends Thread {
public int ticks = 10; // 每個實例擁有獨立 ticks
boolean flag = true;
public MyThread(String name) { this.setName(name); }
@Override public void run() { while(flag) saleTick(); }
public void saleTick() {
if(ticks > 0) { ticks--; System.out.println(Thread.currentThread().getName() + ":" + ticks); }
else flag = false;
}
}
public class MyRunnable implements Runnable {
public int ticks = 10; // 單一實例,數據共享
boolean flag = true;
@Override public void run() { while(flag) saleTick(); }
public void saleTick() {
if(ticks > 0) { ticks--; System.out.println(Thread.currentThread().getName() + ":" + ticks); }
else flag = false;
}
}
public class Test {
public static void main(String[] args) {
// Thread 方式:三個實例,各賣 10 張,共 30 張
new MyThread("thread1").start();
new MyThread("thread2").start();
new MyThread("thread3").start();
// Runnable 方式:一個實例,三個線程共享,共賣 10 張
MyRunnable mr = new MyRunnable();
new Thread(mr, "runnable1").start();
new Thread(mr, "runnable2").start();
new Thread(mr, "runnable3").start();
}
}
輸出結果片段:
thread1:9 thread1:8 ... thread1:0 // thread1 賣完 10 張 thread2:9 thread2:8 ... thread2:0 // thread2 又賣 10 張 thread3:9 ... thread3:0 // thread3 再賣 10 張 runnable1:9 runnable1:8 ... runnable1:0 // 三線程共同賣完 10 張
ticks=10,實際上模擬的是「三個窗口各自有 10 張票」,而非「一個窗口共賣 10 張票」。ExecutorService)使用,實現任務與線程分離,支持線程復用、併發控制、異常處理、返回值獲取。ticks-- 非原子操作,高併發下會出現超賣/少賣。生產環境需用 AtomicInteger、synchronized 或 ReentrantLock 保護臨界區。理解這兩者區別,是寫出正確併發程序的基石。