Java中
Runtime.getInstance().exec (String cmd)
或者
new ProcessBuilder(String cmd).start()
都可以产生子进程对象Process。通过调用Process对象的waitFor()方法可以使主进程进入等待状态,直至子进程执行完毕,再进行下一步工作。如果对子进程处理不当,有可能造成主进程阻塞,整个程序死掉。
java Api中关于Process说的是:
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的输出流和错误流进行处理,未必有效,顺序执行过程中,输出流和错误流常常不能得到及时处理。解决方案有两个。
方案一:并发获取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(); } ) } }
方案二:用ProcessBuilder的redirectErrorStream()方法合并输出流和错误流。
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; }