許多開發者在 Windows 下調用 Runtime.exec("query session") 或 cmd.exe /c dir 時遇到 waitFor() 永不返回、readLine() 阻塞、或拋出 IllegalThreadStateException。根因在於 子進程輸出/錯誤流未及時消費導致緩衝區填滿,引發死鎖。
| 坑點 | 現象 | 根因 | 解法 |
|---|---|---|---|
| 1. 直接調用 exitValue() | IllegalThreadStateException | 進程未結束就取退出碼 | 改用 waitFor() 或捕獲異常 |
| 2. 忽略輸出/錯誤流 | waitFor() 掛起、readLine() 阻塞 | 子進程寫滿管道緩衝區 | 啟動獨立線程消費 InputStream/ErrorStream |
| 3. 將內部命令當可執行程序 | IOException: CreateProcess error=2 | dir、copy 等是 cmd 內建命令 | 用 cmd.exe /c "command" 包裹 |
| 4. 當 Shell 用(管道、重定向) | 重定向失效、參數被錯誤分割 | exec() 不解析 Shell 語法 | 在 Java 代碼中手動處理流重定向/管道 |
class StreamGobbler extends Thread {
private final InputStream is;
private final String type;
private final OutputStream redirect; // 可選:重定向到文件
StreamGobbler(InputStream is, String type) { this(is, type, null); }
StreamGobbler(InputStream is, String type, OutputStream redirect) {
this.is = is; this.type = type; this.redirect = redirect;
}
@Override public void run() {
try (BufferedReader br = new BufferedReader(new InputStreamReader(is, "GBK"));
PrintWriter pw = redirect != null ? new PrintWriter(redirect) : null) {
String line;
while ((line = br.readLine()) != null) {
if (pw != null) pw.println(line);
System.out.println(type + "> " + line);
}
if (pw != null) pw.flush();
} catch (IOException e) { e.printStackTrace(); }
}
}
public static void execWin(String cmd) throws Exception {
String[] cmds = { "cmd.exe", "/C", cmd };
Process proc = Runtime.getRuntime().exec(cmds);
new StreamGobbler(proc.getErrorStream(), "ERROR").start();
new StreamGobbler(proc.getInputStream(), "OUTPUT").start();
int exit = proc.waitFor();
System.out.println("ExitValue: " + exit);
}
pb.redirectOutput(file)),API 更現代。InputStreamReader 時顯式指定。proc.waitFor(timeout, TimeUnit) 避免無限等待。cmd.exe /c 包裹內部命令?掌握以上原則,Runtime.exec() / ProcessBuilder 就不再是「坑」,而是可控的外部進程調用利器。