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;
}
複製程式碼