使用 Runtime.exec() 或 ProcessBuilder 啟動外部程式時,Java 程式常會莫名卡住,不再繼續執行。經排查發現,主因在於子程序的標準輸出與錯誤輸出緩衝區塞滿,導致子程序寫入阻塞,進而造成父程序 waitFor() 永遠等待。
getOutputStream()(寫入子程序)、getInputStream()(讀取子程序標準輸出)、getErrorStream()(讀取子程序錯誤輸出)。InputStream / ErrorStream,緩衝區寫滿 → 子程序 write() 阻塞 → 子程序無法結束 → 父程序 waitFor() 永不返回。必須在獨立執行緒中同時讀取標準輸出與錯誤輸出,主執行緒才能安全呼叫 waitFor()。
private static File toSwf(String sourceFile, String destFile, String command) {
long beginTime = System.nanoTime();
Runtime rt = Runtime.getRuntime();
try {
Process process = rt.exec(command);
// 1. 非同步讀取標準輸出
final InputStream isNormal = process.getInputStream();
new Thread(() -> {
try (BufferedReader br = new BufferedReader(new InputStreamReader(isNormal))) {
StringBuilder buf = new StringBuilder();
String line;
while ((line = br.readLine()) != null) {
buf.append(line).append("\n");
}
System.out.println("標準輸出:" + buf);
} catch (IOException e) {
e.printStackTrace();
}
}).start();
// 2. 主執行緒同步讀取錯誤輸出(或也可另開執行緒)
try (BufferedReader brErr = new BufferedReader(new InputStreamReader(process.getErrorStream()))) {
StringBuilder errBuf = new StringBuilder();
String line;
while ((line = brErr.readLine()) != null) {
errBuf.append(line).append("\n");
}
System.out.println("錯誤輸出:" + errBuf);
}
// 3. 等待子程序結束
int exitCode = process.waitFor();
System.out.println("子程序退出碼:" + exitCode);
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
long endTime = System.nanoTime();
System.out.println("轉檔耗時: " + (endTime - beginTime) / 1_000_000_000 + " 秒");
return new File(destFile);
}
ProcessBuilder,支援 redirectErrorStream(true) 合併錯誤流,簡化讀取邏輯。ProcessHandle API,可非阻塞檢查進程狀態。processBuilder.redirectOutput(Files.newOutputStream(path))。掌握「讀空流再等待」原則,即可徹底解決 Process 阻塞難題。