1. 程式人生 > >Java呼叫其他程式時waitFor()阻塞

Java呼叫其他程式時waitFor()阻塞

前段時間在工作中遇到這樣一個問題,java程式碼中呼叫一個bat程式,在linux下執行完好,但是換到windows下就一直掛在那裡不動了~

程式碼如下:

複製程式碼
public class CMDTest {
    public static void main(String[] args) {
        Process p = null;
        try {
            p = Runtime.getRuntime().exec("c:\\test.bat");

            p.waitFor();
            System.out.println(p.exitValue());
            System.out.println(
"over"); } catch (Exception e) { e.printStackTrace(); } } }
複製程式碼

其中,test.bat裡就是一些命令操作,在這裡就寫一個簡單的ping命令。

ping www.baidu.com

上面的程式碼執行之後,等了半天都沒反應。Process的api中有如下說明:

複製程式碼
ProcessBuilder.start() 和 Runtime.exec 方法建立一個本機程序,並返回 Process 子類的一個例項,該例項可用來控制程序並獲得相關資訊。Process 類提供了執行從程序輸入、執行輸出到程序、等待程序完成、檢查程序的退出狀態以及銷燬(殺掉)程序的方法。 

建立程序的方法可能無法針對某些本機平臺上的特定程序很好地工作,比如,本機視窗程序,守護程序,Microsoft Windows 上的 Win16
/DOS 程序,或者 shell 指令碼。
建立的子程序沒有自己的終端或控制檯。它的所有標準 io(即 stdin、stdout 和 stderr)操作都將通過三個流 (getOutputStream()、getInputStream() 和 getErrorStream()) 重定向到父程序。父程序使用這些流來提供到子程序的輸入和獲得從子程序的輸出。
因為有些本機平臺僅針對標準輸入和輸出流提供有限的緩衝區大小,如果讀寫子程序的輸出流或輸入流迅速出現失敗,則可能導致子程序阻塞,甚至產生死鎖。
複製程式碼

也就是說:如果程式不斷在向標準輸出流和標準錯誤流寫資料,而JVM不讀取的話,當緩衝區滿之後將無法繼續寫入資料,最終造成阻塞在waitFor()這裡。

這就是問題所在!之後我查了下網上的解決辦法,多數是建立兩個執行緒在waitFor()命令之前讀出視窗的標準輸出緩衝區和標準錯誤流的內容。

按照這個思路,我寫了如下util方法

複製程式碼
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;

public class CommandUtil {
    // 儲存程序的輸入流資訊
    private List<String> stdoutList = new ArrayList<String>();
    // 儲存程序的錯誤流資訊
    private List<String> erroroutList = new ArrayList<String>();

    public void executeCommand(String command) {
        // 先清空
        stdoutList.clear();
        erroroutList.clear();

        Process p = null;
        try {
            p = Runtime.getRuntime().exec(command);

            // 建立2個執行緒,分別讀取輸入流緩衝區和錯誤流緩衝區
            ThreadUtil stdoutUtil = new ThreadUtil(p.getInputStream(), stdoutList);
            ThreadUtil erroroutUtil = new ThreadUtil(p.getErrorStream(), erroroutList);
            //啟動執行緒讀取緩衝區資料
            stdoutUtil.start();
            erroroutUtil.start();

            p.waitFor();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public List<String> getStdoutList() {
        return stdoutList;
    }

    public List<String> getErroroutList() {
        return erroroutList;
    }

}

class ThreadUtil implements Runnable {
    // 設定讀取的字元編碼
    private String character = "GB2312";
    private List<String> list;
    private InputStream inputStream;

    public ThreadUtil(InputStream inputStream, List<String> list) {
        this.inputStream = inputStream;
        this.list = list;
    }

    public void start() {
        Thread thread = new Thread(this);
        thread.setDaemon(true);//將其設定為守護執行緒
        thread.start();
    }

    public void run() {
        BufferedReader br = null;
        try {
            br = new BufferedReader(new InputStreamReader(inputStream, character));
            String line = null;
            while ((line = br.readLine()) != null) {
                if (line != null) {
                    list.add(line);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                //釋放資源
                inputStream.close();
                br.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

}
複製程式碼

再整個方法測試下:

複製程式碼
import java.util.List;

public class TestMain {
    public static void main(String[] args) {
        CommandUtil util = new CommandUtil();
        util.executeCommand("c:\\test.bat");
        printList(util.getStdoutList());
        System.out.println("--------------------");
        printList(util.getErroroutList());
    }

    
    public static void printList(List<String> list){
        for (String string : list) {
            System.out.println(string);
        }
    }
    
}
複製程式碼

這樣一來,問題確實解決了,再也不會出現阻塞了