線程是 Java 併發的基本執行單元。本文系統梳理 繼承 Thread 與 實現 Runnable 兩大創建線程方式的本質區別、常見誤區(start() vs run()),並延伸講解 ProcessBuilder / Runtime.exec() 啟動系統進程的底層機制與最佳實踐。
java.exe/javaw.exe。class MyThread extends Thread {
private static int counter = 0;
public MyThread() { counter++; }
@Override
public void run() {
System.out.println("主動創建的第 " + counter + " 個線程,ID: " + Thread.currentThread().getId());
}
}
// 啟動
new MyThread().start(); // 正確:創建新線程執行 run()
// new MyThread().run(); // 錯誤:主線程直接調用 run(),無新線程產生
start():請求 JVM 創建新線程,異步執行 run()。run() 只是普通方法:直接調用不會創建線程,當前線程同步執行。Thread.currentThread().getId() 可直觀區分主/子線程。class MyTask implements Runnable {
@Override
public void run() {
System.out.println("子線程 ID: " + Thread.currentThread().getId());
}
}
// 啟動
Thread thread = new Thread(new MyTask());
thread.start(); // 必須包裹在 Thread 中啟動
Runnable 代表「任務」,Thread 代表「執行載體」,符合單一職責原則。() -> { ... } 簡化代碼(Java 8+)。ExecutorService、FutureTask、CompletableFuture 均接收 Runnable/Callable。Thread 類本身實現了 Runnable,其 run() 默認調用目標 Runnable.run()。繼承 Thread 只是將「任務」與「線程」耦合在一起。
| 維度 | 繼承 Thread | 實現 Runnable |
|---|---|---|
| 代碼簡潔度 | 高(單類完成) | 稍低(需 new Thread) |
| 繼承靈活性 | 差(佔用繼承名額) | 優(可繼承業務類) |
| 任務復用性 | 差 | 優(同一 Runnable 可提交多線程/線程池) |
| 推薦場景 | 簡單測試、教學演示 | 生產代碼、併發框架集成 |
Java 通過 ProcessBuilder(推薦)或 Runtime.exec() 創建操作系統級進程(非 JVM 線程),適用於調用 Shell、Python、exe 等外部程序。
Process:抽象類,代表子進程,提供 getInputStream()、getErrorStream()、waitFor()、destroy()。ProcessBuilder:最終類,構建進程配置(命令、環境變量、工作目錄),start() 返回 ProcessImpl(Process 實現類)。Runtime.exec():內部委託 ProcessBuilder,單例模式獲取 Runtime 實例。ProcessBuilder pb = new ProcessBuilder("cmd", "/c", "ipconfig /all");
// 或 List cmds = Arrays.asList("cmd", "/c", "ipconfig /all");
// 設置工作目錄、環境變量
// pb.directory(new File("C:/temp"));
// pb.environment().put("MY_VAR", "value");
Process process = pb.start();
try (Scanner scanner = new Scanner(process.getInputStream(), "GBK")) {
while (scanner.hasNextLine()) System.out.println(scanner.nextLine());
}
int exitCode = process.waitFor(); // 等待結束,獲取退出碼
String cmd = "cmd /c ipconfig /all"; Process process = Runtime.getRuntime().exec(cmd); // 僅支持單字符串,需自行拼接
注意:exec(String) 不支持參數數組,含空格路徑易出錯,強烈建議使用 ProcessBuilder(String...) 或 List 形式。
Executors.newFixedThreadPool(n) 替代手動 new Thread,控制併發度、復用線程。stdout/stderr 緩衝區滿會阻塞,必須在單獨線程消費或使用 redirectErrorStream(true) 合併流。process.waitFor(timeout, TimeUnit.SECONDS) (Java 9+) 防止掛死。SecurityManager 可能禁止 exec,需配置權限。掌握線程創建本質與進程啟動機制,是編寫高性能、穩健 Java 併發應用的基石。實際開發中,請拥抱 ExecutorService、CompletableFuture 等高級併發工具,將精力聚焦於業務邏輯而非底層線程管理。