首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java Process 執行阻塞問題:輸入流未消耗導致死鎖

     編輯:Java 相關知識

問題背景

使用 Runtime.exec() 或 ProcessBuilder 啟動外部程式時,Java 程式常會莫名卡住,不再繼續執行。經排查發現,主因在於子程序的標準輸出與錯誤輸出緩衝區塞滿,導致子程序寫入阻塞,進而造成父程序 waitFor() 永遠等待。

根本原因

  • Java 進程擁有三個流:getOutputStream()(寫入子程序)、getInputStream()(讀取子程序標準輸出)、getErrorStream()(讀取子程序錯誤輸出)。
  • 作業系統管道緩衝區大小有限(通常 4KB~64KB)。
  • 若子程序產生大量輸出,而父程序未及時讀取 InputStream / ErrorStream,緩衝區寫滿 → 子程序 write() 阻塞 → 子程序無法結束 → 父程序 waitFor() 永不返回。

正確解法:多執行緒非同步消耗流

必須在獨立執行緒中同時讀取標準輸出與錯誤輸出,主執行緒才能安全呼叫 waitFor()。

完整程式碼範例(PDF 轉 SWF 實戰)

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);
}

關鍵要點補充

  1. JDK 8+ 建議使用 ProcessBuilder,支援 redirectErrorStream(true) 合併錯誤流,簡化讀取邏輯。
  2. JDK 9+ 提供 ProcessHandle API,可非阻塞檢查進程狀態。
  3. 若輸出極大,考慮直接重導向到檔案:processBuilder.redirectOutput(Files.newOutputStream(path))。

掌握「讀空流再等待」原則,即可徹底解決 Process 阻塞難題。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved