Runtime.getRuntime().exec() 输出流阻塞问题

时间:2022-05-13 20:32:00

有时候我们可能需要调用系统外部的某个程序,此时就可以用Runtime.getRuntime().exec()来调用,他会生成一个新的进程去运行调用的程序。
此方法返回一个java.lang.Process对象,该对象可以得到之前开启的进程的运行结果,还可以操作进程的输入输出流。

Process对象有以下几个方法:
  1、destroy()      杀死这个子进程
  2、exitValue()      得到进程运行结束后的返回状态
  3、waitFor()       得到进程运行结束后的返回状态,如果进程未运行完毕则等待知道执行完毕
  4、getInputStream()  得到进程的标准输出信息流
  5、getErrorStream()  得到进程的错误输出信息流
  6、getOutputStream() 得到进程的输入流

现在来讲讲exitValue(),当线程没有执行完毕时调用此方法会跑出IllegalThreadStateException异常,最直接的解决方法就是用waitFor()方法代替。

但是waitFor()方法也有很明显的弊端,因为java程序给进程的输出流分配的缓冲区是很小的,有时候当进程输出信息很大的时候回导致缓冲区被填满,如果不及时处理程序会阻塞。如果程序没有对进程的输出流处理的会就会导致执行exec()的线程永远阻塞,进程也不会执行下去直到输出流被处理或者java程序结束。
解决的方法就是处理缓冲区中的信息,开两个线程分别去处理标准输出流和错误输出流。

public class ExecTest {

public static void main(String[] args) throws IOException, InterruptedException {
  String cmd = "cmd /c dir D:\\";
  final Process process = Runtime.getRuntime().exec(cmd);
  printMessage(process.getInputStream());
  printMessage(process.getErrorStream());
  int value = process.waitFor();
  System.out.println(value);
}

private static void printMessage(final InputStream input) {
  new Thread(new Runnable() {
  public void run() {
    BufferedReader bf = new BufferedReader(new InputStreamReader(input));
    String line = null;
    try {
    while((line=bf.readLine())!=null) {
    System.out.println(line);
    }
    } catch (IOException e) {
    e.printStackTrace();
    }
 }
  }).start();
}
}

扩展-1 可以选择将子进程中的输出流重定向到一个文件中。

//这种方式有文章说不是很推荐,原因是Runtime.exec()不能很好的执行复杂命令行 
// > D:\\1.txt 2>&1表示错误输出和标准输出均重定向到1.txt文件
public class ExecTest {

public static void main(String[] args) throws IOException, InterruptedException {
  String cmd = "cmd /c dir D:\\ > D:\\1.txt 2>&1";
  final Process process = Runtime.getRuntime().exec(cmd);
  
  int value = process.waitFor();
  System.out.println(value);
}
}

扩展-2 将父进程的标准输出和错误输出重定向

public class ExecTest {

public static void main(String[] args) throws IOException, InterruptedException {
PrintStream ps = new PrintStream("D:\\log.txt");
System.setErr(ps);
System.setOut(ps);
  String[] cmdLine = new String[]{"python","D:/test.py","are","you","ok?"};
  final Process process = Runtime.getRuntime().exec(cmdLine);
  printMessage(process.getInputStream());
  printMessage(process.getErrorStream());
  int value = process.waitFor();
  System.out.println(value);
}

private static void printMessage(final InputStream input) {
  new Thread(new Runnable() {
  public void run() {
    BufferedReader bf = new BufferedReader(new InputStreamReader(input));
    String line = null;
    try {
    while((line=bf.readLine())!=null) {
    System.out.println(line);
    }
    } catch (IOException e) {
    e.printStackTrace();
    }
 }
  }).start();
}
}

test.py

import sys
import pandas as pd

for i in range(1,len(sys.argv)):
print sys.argv[i]

  • 子进程的标准输出和错误输出转移到父进程中的标准输出,
  • 父进程的标准输出和错误输出重定向到文件log.txt 中。
  • 因此 所有输出结果可在log.txt 中查看。

参考文献:

http://www.cnblogs.com/fclbky/p/6112180.html
介绍Runtime.getRuntime().exec() 输出流阻塞

http://www.jb51.net/article/64183.htm
很好的讲Shell重定向&>file、2>&1、1>&2的区别的文章

http://blog.csdn.net/xiao472613694/article/details/7313657
介绍System.setOut() 用法

http://blog.csdn.net/thorny_v/article/details/61417386
介绍动态传参Java 调用python 脚本

http://search.iteye.com/blog/253257
介绍Runtime.exec的陷阱