【问题标题】:Java exec method, how to handle streams correctlyJava exec方法,如何正确处理流
【发布时间】:2017-08-03 05:37:18
【问题描述】:

从 Java 生成和使用外部进程的流 (IO) 的正确方法是什么?据我所知,由于缓冲区大小可能有限,java 端输入流(进程输出)应该在并行线程中使用,以产生进程输入。

但我不确定我最终是否需要与那些消费者线程同步,还是仅仅等待进程使用waitFor 方法退出就足够了,以确保所有进程输出都被实际消耗? IE是否有可能,即使进程退出(关闭它的输出流),流的java端仍然有未读数据? waitFor 实际上是如何知道该过程何时完成的?对于有问题的进程,EOF(关闭其输入流的 java 端)发出信号退出。

我目前处理流的解决方案如下

public class Application {

    private static final StringBuffer output = new StringBuffer();
    private static final StringBuffer errOutput = new StringBuffer();
    private static final CountDownLatch latch = new CountDownLatch(2);

    public static void main(String[] args) throws IOException, InterruptedException {

        Process exec = Runtime.getRuntime().exec("/bin/cat");

        OutputStream procIn = exec.getOutputStream();
        InputStream procOut = exec.getInputStream();
        InputStream procErrOut = exec.getErrorStream();

        new Thread(new StreamConsumer(procOut, output)).start();
        new Thread(new StreamConsumer(procErrOut, errOutput)).start();

        PrintWriter printWriter = new PrintWriter(procIn);

        printWriter.print("hello world");
        printWriter.flush();
        printWriter.close();

        int ret = exec.waitFor();
        latch.await();

        System.out.println(output.toString());
        System.out.println(errOutput.toString());
    }

    public static class StreamConsumer implements Runnable {

        private InputStream input;
        private StringBuffer output;

        public StreamConsumer(InputStream input, StringBuffer output) {
            this.input = input;
            this.output = output;
        }

        @Override
        public void run() {
            BufferedReader reader = new BufferedReader(new InputStreamReader(input));
            String line;
            try {
                while ((line = reader.readLine()) != null) {
                    output.append(line + System.lineSeparator());
                }
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } finally {
                try {
                    reader.close();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } finally {
                    latch.countDown();
                }
            }
        }

    }
}

这里是否需要使用锁存器,还是waitFor 暗示所有输出都已被消耗?此外,如果输出没有结束/不包含新行,readLine 会错过输出,还是仍然读取剩下的所有内容?读取 null 是否意味着进程已关闭它的流的结尾 - 是否还有其他可以读取 null 的场景?

处理流的正确方法是什么,我能比我的例子做得更好吗?

【问题讨论】:

    标签: java multithreading process io stream


    【解决方案1】:

    您的方法是正确的,但最好删除 CountDownLatch 并使用 ThreadPool,而不是直接创建新线程。从 ThreadPool 中,您将获得两个期货,您可以等待它们完成。

    但我不确定我最终是否需要与那些消费者线程同步,或者仅使用 waitFor 方法等待进程退出,以确保所有进程输出都被实际消耗就足够了吗? IE是否可能,即使进程退出(关闭它的输出流),流的java端仍有未读数据?

    是的,这种情况可能会发生。终止和读取 IO 流是不相关的过程。

    【讨论】:

    • 感谢您的澄清。不幸的是,代码必须是 JDK6 兼容的
    • ThreadPool 从 Java5 开始就存在。见Executors
    【解决方案2】:

    waitFor 表示进程结束,但您不能确定从其 stdout 和 stderr 收集字符串的线程也完成了,因此使用锁存器是朝着正确方向迈出的一步,但不是最佳选择。 您可以直接等待线程,而不是等待闩锁:

    Thread stdoutThread = new Thread(new StreamConsumer(procOut, output)).start();
    Thread stderrThread = ...
    ...
    int ret = exec.waitFor();
    stdoutThread.join();
    stderrThread.join();
    

    顺便说一句,在StringBuffers 中存储行是无用的工作。改用ArrayList<String>,将行放在那里而不进行任何转换,最后在循环中检索它们。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-10-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-03-07
      • 1970-01-01
      相关资源
      最近更新 更多