溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

如何解決Process.getInputStream()阻塞的問題

發(fā)布時(shí)間:2021-06-09 09:47:48 來源:億速云 閱讀:295 作者:小新 欄目:開發(fā)技術(shù)

這篇文章主要介紹了如何解決Process.getInputStream()阻塞的問題,具有一定借鑒價(jià)值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。

Process.getInputStream()阻塞問題

Java中

Runtime.getInstance().exec (String cmd)

或者

new ProcessBuilder(String cmd).start()

都可以產(chǎn)生子進(jìn)程對(duì)象Process。通過調(diào)用Process對(duì)象的waitFor()方法可以使主進(jìn)程進(jìn)入等待狀態(tài),直至子進(jìn)程執(zhí)行完畢,再進(jìn)行下一步工作。如果對(duì)子進(jìn)程處理不當(dāng),有可能造成主進(jìn)程阻塞,整個(gè)程序死掉。

java Api中關(guān)于Process說的是:

ProcessBuilder.start() 和 Runtime.exec 方法創(chuàng)建一個(gè)本機(jī)進(jìn)程,并返回 Process 子類的一個(gè)實(shí)例,該實(shí)例可用來控制進(jìn)程并獲取相關(guān)信息。Process 類提供了執(zhí)行從進(jìn)程輸入、執(zhí)行輸出到進(jìn)程、等待進(jìn)程完成、檢查進(jìn)程的退出狀態(tài)以及銷毀(殺掉)進(jìn)程的方法。

創(chuàng)建進(jìn)程的方法可能無法針對(duì)某些本機(jī)平臺(tái)上的特定進(jìn)程很好地工作,比如,本機(jī)窗口進(jìn)程,守護(hù)進(jìn)程,Microsoft Windows 上的 Win16/DOS 進(jìn)程,或者 shell 腳本。創(chuàng)建的子進(jìn)程沒有自己的終端或控制臺(tái)。它的所有標(biāo)準(zhǔn) io(即 stdin,stdout,stderr)操作都將通過三個(gè)流 (getOutputStream(),getInputStream(),getErrorStream()) 重定向到父進(jìn)程。父進(jìn)程使用這些流來提供到子進(jìn)程的輸入和獲得從子進(jìn)程的輸出。因?yàn)橛行┍緳C(jī)平臺(tái)僅針對(duì)標(biāo)準(zhǔn)輸入和輸出流提供有限的緩沖區(qū)大小,如果讀寫子進(jìn)程的輸出流或輸入流迅速出現(xiàn)失敗,則可能導(dǎo)致子進(jìn)程阻塞,甚至產(chǎn)生死鎖。

在對(duì)getOutputStream(),getInputStream(),getErrorStream()的描述中,有個(gè)注意事項(xiàng):對(duì)其輸出流和錯(cuò)誤流進(jìn)行緩沖是一個(gè)好主意!嗯,好抽象??!

問題正在于此,Process.getInputStream()和Process.getErrorStream()分別返回Process的標(biāo)準(zhǔn)輸出流和錯(cuò)誤流,兩個(gè)流如果處理不當(dāng),其緩沖區(qū)不能被及時(shí)清除而被塞滿,則進(jìn)程被阻塞,即使調(diào)用Process.destory()也未必能銷毀被阻塞的子進(jìn)程。

如果嘗試同步獲取Process的輸出流和錯(cuò)誤流進(jìn)行處理,未必有效,順序執(zhí)行過程中,輸出流和錯(cuò)誤流常常不能得到及時(shí)處理。解決方案有兩個(gè)。

方案一:并發(fā)獲取Process的輸出流和錯(cuò)誤流

通過啟動(dòng)兩個(gè)線程來并發(fā)地讀取和處理輸出流和錯(cuò)誤流,懶得打開IDE了,就大概敲一下代碼吧,可能有錯(cuò)誤,如下:

調(diào)用者:

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()方法合并輸出流和錯(cuò)誤流

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

Java Process 阻塞測試總結(jié)

Process阻塞原因:輸入流和錯(cuò)誤流分開的,沒有處理,就會(huì)發(fā)生阻塞,歸根結(jié)底本質(zhì)上是bio引起的io阻塞問題。

getInputStream,getErrorSteam就是獲取腳本或者命令的控制臺(tái)回顯信息,前者獲取的是標(biāo)準(zhǔn)輸出的回顯信息,后者獲取的是標(biāo)準(zhǔn)錯(cuò)誤的回顯信息

Process原理:使用Runtime.getRuntime().exec(cmd)會(huì)在當(dāng)前進(jìn)程建立一個(gè)子進(jìn)程,子進(jìn)程由于沒有控制臺(tái),它的標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯(cuò)誤就會(huì)返回給父進(jìn)程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 {
                        //執(zhí)行命令
                        p = Runtime.getRuntime().exec(cmd);
                        //取得命令結(jié)果的輸出流
                        //輸出流
                        InputStream fis=p.getInputStream();
                        //錯(cuò)誤流
                        InputStream ferrs=p.getErrorStream();
                        //用一個(gè)讀輸出流類去讀
                        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) {
                        //有可能發(fā)生阻塞的問題
                                System.out.println("return input Str:" + line);
                        }
                        while((lineerr=errbr.readLine())!=null){
                        //有可能發(fā)生阻塞的問題
                                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
         //輸出的標(biāo)準(zhǔn)輸出
        echo "testaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
        //輸出到標(biāo)準(zhǔn)錯(cuò)誤
        echo "testaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 1>&2
done

經(jīng)過測試發(fā)現(xiàn),如果JavaExeBat.java文件中只開啟標(biāo)準(zhǔn)輸出或者標(biāo)準(zhǔn)錯(cuò)誤時(shí),進(jìn)程就會(huì)夯住,無法通過waiteFor獲取其返回值,因?yàn)槟_本中分別輸出了100000w條信息到標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯(cuò)誤,而下述代碼只處理了getInputStream,導(dǎo)致標(biāo)準(zhǔn)錯(cuò)誤輸出流的信息太多返回給當(dāng)前進(jìn)程,沒有得到處理,因此阻塞。

代碼如下:

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

把上述代碼中的getInputStream換做getErrorStream,也會(huì)夯住進(jìn)程,因?yàn)橥瑯又惶幚砹藘烧咧幸徽?,即?biāo)準(zhǔn)錯(cuò)誤。

那么能不能同步處理兩個(gè)流信息呢?代碼如下:

try {
                        //執(zhí)行命令
                        p = Runtime.getRuntime().exec(cmd);
                        //取得命令結(jié)果的輸出流
                        //輸出流
                        InputStream fis=p.getInputStream();
                        //錯(cuò)誤流
                        InputStream ferrs=p.getErrorStream();
                        //用一個(gè)讀輸出流類去讀
                        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) {
                        //有可能發(fā)生阻塞的問題
                                System.out.println("return input Str:" + line);
                        }
                        while((lineerr=errbr.readLine())!=null){
                        //有可能發(fā)生阻塞的問題
                                System.out.println("return err Str:" + lineerr);
                        }
                        int exitVal = p.waitFor();
                        System.out.println("exitVal:" + exitVal);
                } catch (Exception e) {
                        e.printStackTrace();
                }
        }

測試過后發(fā)現(xiàn)也不行,因?yàn)槭峭降?,就?huì)有先后順序,也會(huì)發(fā)生阻塞,測試方法,將test.sh改為只打印標(biāo)準(zhǔn)錯(cuò)誤,就會(huì)發(fā)現(xiàn)標(biāo)準(zhǔn)錯(cuò)誤處理被阻塞,腳本如下:

#!/bin/bash
 
for((i=0; i < 100000; i++));do
        //輸出到標(biāo)準(zhǔn)錯(cuò)誤
        echo "testaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 1>&2
done

解決辦法思路:

(1)并發(fā)處理兩個(gè)流信息,開啟兩個(gè)線程分別處理輸出流與錯(cuò)誤流

(2)將兩個(gè)流合并為一個(gè)流解決示例:

第一種思路:

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);將輸出流與錯(cuò)誤流合并

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

感謝你能夠認(rèn)真閱讀完這篇文章,希望小編分享的“如何解決Process.getInputStream()阻塞的問題”這篇文章對(duì)大家有幫助,同時(shí)也希望大家多多支持億速云,關(guān)注億速云行業(yè)資訊頻道,更多相關(guān)知識(shí)等著你來學(xué)習(xí)!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI