Java Process.exec阻塞问题调查

Process原理

通过Java创建子进程,利用进程间的无名管道进行传输标准I/O流

管道原理

实现原理:内核借助环型队列机制,使用内核缓冲区实现。

管道的限制:

  1. 数据不能进程自己写,自己读
  2. 管道中数据不可以反复读取。一旦读走,管道中不再存在。
  3. 采用半双工通信方式,数据只能在单方向上流动。
  4. 只能在有公共祖先的进程间使用管道。

管道读写行为:

读管道:
  1. 管道有数据,read返回实际读到的字节数
  2. 管道无数据:
    • 管道写端全部被关闭时,read返回0。
    • 管道写端没有全部被关闭,read阻塞等待。
写管道:
  1. 管道读端全部被关闭,进程异常终止(只有在管道的读端存在时,向管道中写入数据才有意义。否则,向管道中写入数据的进程将收到内核传来的SIFPIPE信号, 应用程序可以处理该信号,也可以忽略(默认动作则是应用程序终止))
  2. 管道读端没有全部关闭
    • 管道已满,write阻塞等待
    • 管道未满,write将数据写入,并返回实际写入的字节数。

Process执行逻辑

  • 主进程中调用Runtime.exec会创建一个子进程,用于执行脚本。子进程创建后会和主进程分别独立运行。

  • 创建的子进程没有自己的终端或控制台。它的所有标准 io(即 stdin、stdout 和 stderr)操作都将通过三个流 (getOutputStream()、getInputStream() 和 getErrorStream()) 通过管道输入端重定向到父进程。父进程使用这些流来提供到子进程的输入和获得从子进程的输出。

  • 这时候子进程不断向主进程发生数据,而主进程调用Process.waitfor后已挂起。当前子进程和主进程之间的缓冲区塞满后,子进程不能继续写数据,然后会挂起。这样子进程等待主进程读取数据,主进程等待子进程结束发送EOF,两个进程相互等待,最终导致死锁。

java 复制代码
/**
** 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;

解决方法

  1. 在waitFor()之前,利用单独两个线程,分别处理process的getInputStream()和getErrorSteam(),防止缓冲区被撑满,导致阻塞;

缺点:标准输入流和标准输出流会堆在一块输出。方案二没有该问题

  1. 利用ProcessBuilder.redirectErrorStream()重定向错误流到输入流中

其他知识

  1. ProcessBuilder有一个inheritIO()方法 这个方法等同于
java 复制代码
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的控制台中

评论区 (0 条评论)

暂无评论,来发表第一条评论吧!

使用 Nuxt 3 构建 | 部署于 Docker | 托管于 狗云
Copyright © 2020-2026 | 网站已续航 2166 天