您好,登錄后才能下訂單哦!
這篇文章主要介紹了如何解決Process.getInputStream()阻塞的問題,具有一定借鑒價值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。
Java中
Runtime.getInstance().exec (String cmd)
或者
new ProcessBuilder(String cmd).start()
都可以產生子進程對象Process。通過調用Process對象的waitFor()方法可以使主進程進入等待狀態,直至子進程執行完畢,再進行下一步工作。如果對子進程處理不當,有可能造成主進程阻塞,整個程序死掉。
ProcessBuilder.start() 和 Runtime.exec 方法創建一個本機進程,并返回 Process 子類的一個實例,該實例可用來控制進程并獲取相關信息。Process 類提供了執行從進程輸入、執行輸出到進程、等待進程完成、檢查進程的退出狀態以及銷毀(殺掉)進程的方法。
創建進程的方法可能無法針對某些本機平臺上的特定進程很好地工作,比如,本機窗口進程,守護進程,Microsoft Windows 上的 Win16/DOS 進程,或者 shell 腳本。創建的子進程沒有自己的終端或控制臺。它的所有標準 io(即 stdin,stdout,stderr)操作都將通過三個流 (getOutputStream(),getInputStream(),getErrorStream()) 重定向到父進程。父進程使用這些流來提供到子進程的輸入和獲得從子進程的輸出。因為有些本機平臺僅針對標準輸入和輸出流提供有限的緩沖區大小,如果讀寫子進程的輸出流或輸入流迅速出現失敗,則可能導致子進程阻塞,甚至產生死鎖。
在對getOutputStream(),getInputStream(),getErrorStream()的描述中,有個注意事項:對其輸出流和錯誤流進行緩沖是一個好主意!嗯,好抽象啊!
問題正在于此,Process.getInputStream()和Process.getErrorStream()分別返回Process的標準輸出流和錯誤流,兩個流如果處理不當,其緩沖區不能被及時清除而被塞滿,則進程被阻塞,即使調用Process.destory()也未必能銷毀被阻塞的子進程。
如果嘗試同步獲取Process的輸出流和錯誤流進行處理,未必有效,順序執行過程中,輸出流和錯誤流常常不能得到及時處理。解決方案有兩個。
通過啟動兩個線程來并發地讀取和處理輸出流和錯誤流,懶得打開IDE了,就大概敲一下代碼吧,可能有錯誤,如下:
調用者:
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(); } ) } }
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; }
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(); } ) } }
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; }
感謝你能夠認真閱讀完這篇文章,希望小編分享的“如何解決Process.getInputStream()阻塞的問題”這篇文章對大家有幫助,同時也希望大家多多支持億速云,關注億速云行業資訊頻道,更多相關知識等著你來學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。