創建執行緒是併發編程的第一步。Java 提供兩條路徑:實作 Runnable 介面與繼承 Thread 類。本文通過完整可運行範例,對比兩者優劣,並示範 start() vs run()、sleep()、join() 關鍵方法應用。
public class RunnableDemo {
public static void main(String[] args) {
// 1. 建立任務物件
Task task = new Task("Worker-1");
// 2. 包裝成 Thread 並啟動
Thread t = new Thread(task, "Worker-1");
t.start(); // 關鍵:呼叫 start() 才會開啟新執行緒執行 run()
// 主執行緒同步執行
for (int i = 5; i > 0; i--) {
System.out.println("Main: " + i);
sleep(1000);
}
System.out.println("Main thread exiting.");
}
static void sleep(long ms) {
try { Thread.sleep(ms); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
}
}
// 任務類:實作 Runnable,分離「做什麼」與「誰跑」
class Task implements Runnable {
private final String name;
Task(String name) { this.name = name; }
@Override public void run() {
for (int i = 5; i > 0; i--) {
System.out.println(name + ": " + i);
RunnableDemo.sleep(500); // 子執行緒較快
}
System.out.println(name + " exiting.");
}
}
Worker-1: 5
Main: 5
Worker-1: 4
Worker-1: 3
Main: 4
Worker-1: 2
Worker-1: 1
Worker-1 exiting.
Main: 3
Main: 2
Main: 1
Main thread exiting.
class MyThread extends Thread {
MyThread(String name) { super(name); } // 呼叫 Thread(String) 建構子設定名稱
@Override public void run() {
for (int i = 5; i > 0; i--) {
System.out.println(getName() + ": " + i);
try { Thread.sleep(500); } catch (InterruptedException e) { interrupt(); }
}
System.out.println(getName() + " exiting.");
}
}
public class ExtendThreadDemo {
public static void main(String[] args) {
new MyThread("Worker-2").start();
for (int i = 5; i > 0; i--) {
System.out.println("Main: " + i);
try { Thread.sleep(1000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
}
System.out.println("Main thread exiting.");
}
}
| 維度 | Runnable | Thread 繼承 |
|---|---|---|
| 耦合度 | 低:任務與執行載體分離 | 高:任務綁定執行緒 |
| 複用性 | 高:同一 Runnable 可提交給多執行緒/線程池 | 低:每次 new 新實例 |
| 繼承限制 | 無:可繼承其他類 | 受限:Java 單繼承,已佔用 extends |
| Lambda 支援 | 完美(函數式介面) | 不支援 |
| 適用場景 | 99% 場景、線程池、非同步任務 | 極少數需覆寫 Thread 方法(如 interrupt() 行為) |
start():啟動新執行緒,由 JVM 安排呼叫 run()。只能呼叫一次,再次呼叫拋 IllegalThreadStateException。run():普通方法呼叫,在當前執行緒同步執行,**不會開啟新執行緒**。Thread.sleep(ms) 讓**當前執行緒**進入 TIMED_WAITING。synchronized 鎖時 sleep 會阻塞其他執行緒)。InterruptedException,**最佳實踐:捕捉後重設中斷標誌 Thread.currentThread().interrupt()**。wait()。t.join();。
// 正確的中斷處理模式
while (!Thread.currentThread().isInterrupted()) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 重設標誌
break; // 或拋出、返回
}
doWork();
}
中斷是**請求**,非強制終止。被攔截方法(sleep/wait/join、I/O 阻塞)會拋異常/返回;純計算迴圈需主動檢查 isInterrupted()。
// 固定大小線程池
ExecutorService executor = Executors.newFixedThreadPool(4);
// 提交 Runnable
executor.submit(() -> System.out.println("Task running on " + Thread.currentThread().getName()));
// 提交 Callable 獲取 Future
Future future = executor.submit(() -> { compute(); return 42; });
try { Integer result = future.get(5, TimeUnit.SECONDS); }
catch (TimeoutException e) { future.cancel(true); }
// 關閉
executor.shutdown(); // 拒絕新任務,等待現有完成
executor.awaitTermination(1, TimeUnit.MINUTES);
優勢:生命週期管理、佇列緩衝、執行緒複用、異步結果、定時/週期任務(ScheduledExecutorService)。
理解 Runnable 與 Thread 區別、start/run 機制、中斷協作模式,是寫出正確併發代碼的基石。實務上**直接使用 ExecutorService**,將任務提交邏輯與執行策略解耦,避免手動管理執行緒的坑。