Java Process 阻塞測試總結

加瓦攻城獅發表於2018-01-19

Process阻塞原因:輸入流和錯誤流分開的,沒有處理,就會發生阻塞,歸根結底本質上是bio引起的io阻塞問題。
getInputStream,getErrorSteam就是獲取指令碼或者命令的控制檯回顯資訊,前者獲取的是標準輸出的回顯資訊,後者獲取的是標準錯誤的回顯資訊
Process原理:使用Runtime.getRuntime().exec(cmd)會在當前程式建立一個子程式,子程式由於沒有控制檯,它的標準輸出和標準錯誤就會返回給父程式Process,因此通過getInputStream和getErrorStream就可以獲取到這些資訊。

測試程式碼如下:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

public class JavaExeBat {


        public JavaExeBat() {

        }

        public static void main(String[] args) {
                Process p;
                //test.bat中的命令是ipconfig/all
                String cmd="sh test.sh ";
                //String cmd="ping 127.0.0.1 -c 4";

                try {
                        //執行命令
                        p = Runtime.getRuntime().exec(cmd);
                        //取得命令結果的輸出流
                        //輸出流
                        InputStream fis=p.getInputStream();
                        //錯誤流
                        InputStream ferrs=p.getErrorStream();
                        //用一個讀輸出流類去讀
                        InputStreamReader isr=new InputStreamReader(fis);
                        InputStreamReader errsr=new InputStreamReader(ferrs);
                        //用緩衝器讀行
                        BufferedReader br=new BufferedReader(isr);
                        BufferedReader errbr=new BufferedReader(errsr);
                        String line=null;
                        String lineerr = null;
                        //直到讀完為止
                        while((line=br.readLine())!=null) {
                        //有可能發生阻塞的問題
                                System.out.println("return input Str:" + line);
                        }
                        while((lineerr=errbr.readLine())!=null){
                        //有可能發生阻塞的問題
                                System.out.println("return err Str:" + lineerr);
                        }
                        int exitVal = p.waitFor();
                        System.out.println("exitVal:" + exitVal);
                } catch (Exception e) {
                        e.printStackTrace();
                }
        }
}
複製程式碼

test.sh如下

#!/bin/bash


for((i=0; i < 100000; i++));do
         //輸出的標準輸出
        echo "testaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
        //輸出到標準錯誤
        echo "testaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 1>&2
done
複製程式碼

經過測試發現,如果JavaExeBat.java檔案中只開啟標準輸出或者標準錯誤時,程式就會夯住,無法通過waiteFor獲取其返回值,因為指令碼中分別輸出了100000w條資訊到標準輸出和標準錯誤,而下述程式碼只處理了getInputStream,導致標準錯誤輸出流的資訊太多返回給當前程式,沒有得到處理,因此阻塞。程式碼如下:

                        p = Runtime.getRuntime().exec(cmd);
                        //取得命令結果的輸出流
                        //輸出流
                        InputStream fis=p.getInputStream();
                        //用一個讀輸出流類去讀
                        InputStreamReader isr=new InputStreamReader(fis);
                        //用緩衝器讀行
                        BufferedReader br=new BufferedReader(isr);
                        String line=null;
                        //直到讀完為止
                        while((line=br.readLine())!=null) {
                        //有可能發生阻塞的問題
                                System.out.println("return input Str:" + line);
                        }
                        int exitVal = p.waitFor();
                        System.out.println("exitVal:" + exitVal);
複製程式碼

把上述程式碼中的getInputStream換做getErrorStream,也會夯住程式,因為同樣只處理了兩者中一者,即標準錯誤。

那麼能不能同步處理兩個流資訊呢?程式碼如下:

           try {
                        //執行命令
                        p = Runtime.getRuntime().exec(cmd);
                        //取得命令結果的輸出流
                        //輸出流
                        InputStream fis=p.getInputStream();
                        //錯誤流
                        InputStream ferrs=p.getErrorStream();
                        //用一個讀輸出流類去讀
                        InputStreamReader isr=new InputStreamReader(fis);
                        InputStreamReader errsr=new InputStreamReader(ferrs);
                        //用緩衝器讀行
                        BufferedReader br=new BufferedReader(isr);
                        BufferedReader errbr=new BufferedReader(errsr);
                        String line=null;
                        String lineerr = null;
                        //直到讀完為止
                        while((line=br.readLine())!=null) {
                        //有可能發生阻塞的問題
                                System.out.println("return input Str:" + line);
                        }
                        while((lineerr=errbr.readLine())!=null){
                        //有可能發生阻塞的問題
                                System.out.println("return err Str:" + lineerr);
                        }
                        int exitVal = p.waitFor();
                        System.out.println("exitVal:" + exitVal);
                } catch (Exception e) {
                        e.printStackTrace();
                }
        }
複製程式碼

測試過後發現也不行,因為是同步的,就會有先後順序,也會發生阻塞,測試方法,將test.sh改為只列印標準錯誤,就會發現標準錯誤處理被阻塞,指令碼如下:

#!/bin/bash


for((i=0; i < 100000; i++));do
        //輸出到標準錯誤
        echo "testaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 1>&2
done
複製程式碼

解決辦法思路:

  • (1)併發處理兩個流資訊,開啟兩個執行緒分別處理輸出流與錯誤流
  • (2)將兩個流合併為一個流 解決示例:
  • 第一種思路:
    class ProcessExecutor  
    {  
        private Process p;  
        private List<String> outputList;  
        private List<String> errorOutputList;  
        public ProcessExecutor(Process p) throws IOException  
        {  
            if(null == p)  
            {  
                throw new IOException("the provided Process is null");  
            }  
            this. p = p;  
        }  
        public List<String> getOutputList()  
        {  
            return this. outputList;  
        }  
        public List<String> getErrorOutputList()  
        {  
            return this.errorOutputList;  
        }  
        public int execute()  
        {  
            int rs = 0;  
            Thread outputThread = new ProcessOutputThread(this.p.getInputStream());  
            Thread errorOutputThread = new ProcessOutputThread(this.p.getErrorStream());  
            outputThread.start();  
            errorOutputThread.start();  
            rs = p.waitFor();  
            outputThread.join();  
            errorOutputThread.join();  
            this.outputList = outputThread.getOutputList();  
            this.errorOutputList = errorOutputThread.getOutputList();  
            return rs;  
        }  
    }  
    
    class ProcessOutputThread extends Thread  
    {  
        private InputStream is;  
        private List<String> outputList;  
        public ProcessOutputThread(InputStream is) throws IOException  
        {  
            if(null == is)  
            {  
                throw new IOException("the provided InputStream is null");  
            }  
            this. is = is;  
            this.outputList = new ArrayList<String>();  
        }  
        public List<String> getOutputList()  
        {  
            return this. outputList;  
        }  
        @Override  
        public void run()  
        {  
            InputStreamReader ir = null;  
            BufferedReader br = null;  
            try  
            {  
                ir = new InputStreamReader(this.is);  
                br = new BufferedReader(ir);  
                String output = null;  
                while(null != (output = br.readLine()))  
                {  
                    print(output);  
                    this.outputList.add(output);  
                }  
            }  
            catch(IOException e)  
            {  
                e.print();  
            }  
            finally  
            (  
                try  
                {  
                    if(null != br)  
                    {  
                        br.close();  
                    }  
                    if(null != ir)  
                    {  
                        ir.close();  
                    }  
                    if(null != this.is)  
                    {  
                        this.is.close();  
                    }  
                }  
                catch(IOException e)  
                {  
                    e.print();  
                }  
            )  
        }  
    }  
    
複製程式碼
  • 第二種思路:使用ProcessBuilder,將其redirectErrorStream(true);將輸出流與錯誤流合併
 public int execute()  
    {  
        int rs = 0;  
        String[] cmds = {...};//command and arg    
        ProcessBuilder builder = new ProcessBuilder(cmds);    
        builder.redirectErrorStream(true);    
        Process process = builder.start();    
        BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()));    
        String output = null;    
        while (null != (readLine = br.readLine()))  
        {    
            print(output);     
        }    
        rs = process.waitFor();  
        return rs;  
    }  
複製程式碼

相關文章