Process原理
通过Java创建子进程,利用进程间的无名管道进行传输标准I/O流
管道原理
实现原理:内核借助环型队列机制,使用内核缓冲区实现。
管道的限制:
- 数据不能进程自己写,自己读
- 管道中数据不可以反复读取。一旦读走,管道中不再存在。
- 采用半双工通信方式,数据只能在单方向上流动。
- 只能在有公共祖先的进程间使用管道。
管道读写行为:
读管道:
- 管道有数据,read返回实际读到的字节数
- 管道无数据:
- 管道写端全部被关闭时,read返回0。
- 管道写端没有全部被关闭,read阻塞等待。
写管道:
- 管道读端全部被关闭,进程异常终止(只有在管道的读端存在时,向管道中写入数据才有意义。否则,向管道中写入数据的进程将收到内核传来的SIFPIPE信号, 应用程序可以处理该信号,也可以忽略(默认动作则是应用程序终止))
- 管道读端没有全部关闭
- 管道已满,write阻塞等待
- 管道未满,write将数据写入,并返回实际写入的字节数。
Process执行逻辑
-
主进程中调用Runtime.exec会创建一个子进程,用于执行脚本。子进程创建后会和主进程分别独立运行。
-
创建的子进程没有自己的终端或控制台。它的所有标准 io(即 stdin、stdout 和 stderr)操作都将通过三个流 (getOutputStream()、getInputStream() 和 getErrorStream()) 通过管道输入端重定向到父进程。父进程使用这些流来提供到子进程的输入和获得从子进程的输出。
-
这时候子进程不断向主进程发生数据,而主进程调用Process.waitfor后已挂起。当前子进程和主进程之间的缓冲区塞满后,子进程不能继续写数据,然后会挂起。这样子进程等待主进程读取数据,主进程等待子进程结束发送EOF,两个进程相互等待,最终导致死锁。
/**
** This method blocks until input data is available, end of file is detected, or an exception is thrown.
*/
public int read(byte[] b, nt off, int len) throws IOException;
解决方法
- 在waitFor()之前,利用单独两个线程,分别处理process的getInputStream()和getErrorSteam(),防止缓冲区被撑满,导致阻塞;
缺点:标准输入流和标准输出流会堆在一块输出。方案二没有该问题
- 利用
ProcessBuilder.redirectErrorStream()
重定向错误流到输入流中
其他知识
- ProcessBuilder有一个inheritIO()方法 这个方法等同于
processBuilder.redirectError(Redirect.INHERIT);
processBuilder.redirectInput(Redirect.INHERIT);
processBuilder.redirectOutput(Redirect.INHERIT);
JDK中对Redirect.INHERIT描述
Indicates that subprocess I/O source or destination will be the same as those of the current process. This is the normal behavior of most operating system command interpreters (shells).
个人理解就是把子进程的标准输入流、标准输出流、标准错误流都重定向到当前进程(Java进程)
实际效果就是将子进程的流输出到Java的控制台中