解決waitfor()阻塞問題


運行代碼執行exe,shell這樣的程序或腳本再java中需:

     (1) 使用Runtime的exec()方法

     (2) 使用ProcessBuilder的start()方法

Runtime和ProcessBulider提供了不同的方式來啟動程序,設置啟動參數、環境變量和工作目錄。

但是這兩種方法都會返回一個用於管理操作系統進程的Process對象。這個對象中的waitFor()是我們今天要討論的重點。

Process的api中有如下說明:

ProcessBuilder.start() 和 Runtime.exec 方法創建一個本機進程,並返回 Process 子類的一個實例,該實例可用來控制進程並獲得相關信息。Process 類提供了執行從進程輸入、執行輸出到進程、等待進程完成、檢查進程的退出狀態以及銷毀(殺掉)進程的方法。 

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

當Runtime對象調用exec(cmd)后,JVM會啟動一個子進程,該進程會與JVM進程建立三個管道連接:標准輸入,標准輸出和標准錯誤流。

也就是說:如果程序不斷在向標准輸出流和標准錯誤流寫數據,而JVM不讀取的話,當緩沖區滿之后將無法繼續寫入數據,最終造成阻塞在waitFor()這里。

多數是創建兩個線程在waitFor()命令之前讀出窗口的標准輸出緩沖區和標准錯誤流的內容。

package DailyTest;

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> stdotList = new ArrayList<>();
    //保存進程標准錯誤流信息
    private List<String> errorList = new ArrayList<>();

    public void executeCommand(String command){
        stdotList.clear();
        errorList.clear();

        Process p =null;
        try {
            p = Runtime.getRuntime().exec(command);
            //創建兩個線程  分別讀取輸入流緩沖區和錯誤流緩沖區
            ThreadUtil stdotThread = new ThreadUtil(stdotList,p.getInputStream());
            ThreadUtil errorThread = new ThreadUtil(errorList,p.getErrorStream());

            stdotThread.start();
            errorThread.start();

            p.waitFor();
            //一直掛起,直到子進程執行結束
            //返回值0表示正常退出

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

    }

    public List<String> getErrorList() {
        return errorList;
    }

    public List<String> getStdotList() {
        return stdotList;
    }
}

class ThreadUtil implements Runnable{

    //屬於單字節編碼,最多能表示的字符范圍是0-255,應用於英文系列。
    private String character1 = "ISO-8859-1";
    //漢子的國標碼,專門用來表示漢字,是雙字節編碼,而英文字母和iso8859-1一致(兼容iso8859-1編碼)。
    // 其中gbk編碼能夠用來同時表示繁體字和簡體字,而gb2312只能表示簡體字,gbk是兼容gb2312編碼的。
    private String character2 = "GB2312";
    //最統一的編碼,可以用來表示所有語言的字符,而且是定長雙字節(也有四字節的)編碼,包括英文字母在內。
    private String character3 = "unicode";
    //相比於unicode會使用更少的空間,但如果已經知道是漢字 推薦GB2312
    private String character4 = "utf-8";



    private List<String> list;
    private InputStream inputStream;

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

    public void  start(){
        Thread thread = new Thread(this);
        thread.setDaemon(true); //設置為守護線程
        //定義:守護線程--也稱“服務線程”,在沒有用戶線程可服務時會自動離開。優先級:守護線程的優先級比較低,用於為系統中的其它對象和線程提供服務。
        thread.start();
    }

    @Override
    public void run() {
        BufferedReader br =null;
        try {
            br = new BufferedReader(new InputStreamReader(inputStream,character2));
            String line =null;
            while (null != (line = br.readLine())){
                list.add(line);
            }

        }catch (IOException e){
            e.printStackTrace();
        }finally {
            try {
                inputStream.close();
                br.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }


    }
}
package DailyTest;

import java.util.List;

public class TestCommandUtil {
    //快速修改類名  shift+F6
    public static void  main(String args []){
        CommandUtil commandUtil =new CommandUtil();
        commandUtil.executeCommand("javac");
        printList(commandUtil.getStdotList());
        System.out.println("--------------------");
        printList(commandUtil.getErrorList());
    }


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


    }

}

 這個方法確實可以解決調用waitFor()方法阻塞無法返回的問題。

問題的關鍵是處在輸入流緩沖區那個地方,子進程的產生的輸出流沒有被JVM及時的讀取最后緩沖區滿了就卡住了。

至於能夠不讓子進程向輸入流寫入數據,是不是可以解決這個問題,還有待考證。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM