解決Process.getInputStream()阻塞的問題
Process.getInputStream()阻塞問題
Java中
Runtime.getInstance().exec (String cmd)
或者
new ProcessBuilder(String cmd).start()
都可以產(chǎn)生子進(jìn)程對象Process。通過調(diào)用Process對象的waitFor()方法可以使主進(jìn)程進(jìn)入等待狀態(tài),直至子進(jìn)程執(zhí)行完畢,再進(jìn)行下一步工作。如果對子進(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)程的方法可能無法針對某些本機(jī)平臺上的特定進(jìn)程很好地工作,比如,本機(jī)窗口進(jìn)程,守護(hù)進(jìn)程,Microsoft Windows 上的 Win16/DOS 進(jìn)程,或者 shell 腳本。創(chuàng)建的子進(jìn)程沒有自己的終端或控制臺。它的所有標(biāo)準(zhǔn) io(即 stdin,stdout,stderr)操作都將通過三個(gè)流 (getOutputStream(),getInputStream(),getErrorStream()) 重定向到父進(jìn)程。父進(jìn)程使用這些流來提供到子進(jìn)程的輸入和獲得從子進(jìn)程的輸出。因?yàn)橛行┍緳C(jī)平臺僅針對標(biāo)準(zhǔn)輸入和輸出流提供有限的緩沖區(qū)大小,如果讀寫子進(jìn)程的輸出流或輸入流迅速出現(xiàn)失敗,則可能導(dǎo)致子進(jìn)程阻塞,甚至產(chǎn)生死鎖。
在對getOutputStream(),getInputStream(),getErrorStream()的描述中,有個(gè)注意事項(xiàng):對其輸出流和錯(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ò)誤流
通過啟動兩個(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ò)誤流分開的,沒有處理,就會發(fā)生阻塞,歸根結(jié)底本質(zhì)上是bio引起的io阻塞問題。
getInputStream,getErrorSteam就是獲取腳本或者命令的控制臺回顯信息,前者獲取的是標(biāo)準(zhǔn)輸出的回顯信息,后者獲取的是標(biāo)準(zhǔn)錯(cuò)誤的回顯信息
Process原理:使用Runtime.getRuntime().exec(cmd)會在當(dāng)前進(jìn)程建立一個(gè)子進(jìn)程,子進(jìn)程由于沒有控制臺,它的標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯(cuò)誤就會返回給父進(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)程就會夯住,無法通過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,也會夯住進(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)槭峭降?,就會有先后順序,也會發(fā)生阻塞,測試方法,將test.sh改為只打印標(biāo)準(zhǔn)錯(cuò)誤,就會發(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;
}
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
利用Spring boot如何創(chuàng)建簡單的web交互應(yīng)用
這篇文章主要介紹了利用Spring boot如何創(chuàng)建簡單的web交互應(yīng)用的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家具有一定的參考價(jià)值,需要的朋友們下面來一起看看吧。2017-04-04
springboot實(shí)現(xiàn)配置本地訪問端口及路徑
這篇文章主要介紹了springboot實(shí)現(xiàn)配置本地訪問端口及路徑,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01
MyBatis傳入?yún)?shù)為List對象的實(shí)現(xiàn)
這篇文章主要介紹了MyBatis傳入?yún)?shù)為List對象的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03
SpringBoot整合SQLite數(shù)據(jù)庫全過程
sqlite是一個(gè)很輕量級的數(shù)據(jù)庫,可以滿足日常sql的需求,下面這篇文章主要給大家介紹了關(guān)于SpringBoot整合SQLite數(shù)據(jù)庫的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-03-03

