了解I / O阻塞

时间:2017-05-05 12:56:16

标签: java linux

我有一个二进制linux可执行文件,它在stdout中输出一些字节。我从Java应用程序中消耗这些字节:

String[] cmd;
Process p = Runtime.getRuntime().exec(cmd);
InputStream is = p.getInputStream();
int r = is.read();
while(r != -1){
    System.out.println(r);
    r = is.read(); //1
}

但是经过一段时间的工作后,//1永远被阻止了I / O(死锁)。我创建了线程转储并注意到了

"pool-2-thread-1@627" prio=5 tid=0xd nid=NA runnable
  java.lang.Thread.State: RUNNABLE
      at java.io.FileInputStream.readBytes(FileInputStream.java:-1)
      at java.io.FileInputStream.read(FileInputStream.java:255)
      at java.io.BufferedInputStream.read1(BufferedInputStream.java:284)
      at java.io.BufferedInputStream.read(BufferedInputStream.java:345)
      - locked <0x2c0> (a java.lang.UNIXProcess$ProcessPipeInputStream)
      at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
      at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
      at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)

它锁定了原生方法

private native int readBytes(byte[] var1, int var2, int var3) throws IOException;

这种方法永远被阻止的可能原因是什么?也许这是特定于平台的。我使用Ubuntu 16.04

2 个答案:

答案 0 :(得分:1)

您的InputStream正在等待进程 提供另一个字节,或关闭流(通过完成)。

这个过程可能不会做这些事情的一个原因是,它被写入stderr,并在写入时阻止。

您的shell和Java使用的环境之间的环境可能存在差异,这意味着命令导致输出到stderr(例如,程序不在$PATH;您没有足够的权限;你的CWD不是你所期望的等等。)

快速而又肮脏的方法来确定这是否是问题,是将stderr重定向到stdout - 使用ProcessBuilder.redirectErrorStream()或使用2>&1 UNIX方面的事情。

如果就是这种情况,那么 是一种死锁形式--Java程序正在等待本机程序写入一个管道。本机程序正在等待Java程序从另一个程序读取。

处理它的干净方法,你可以选择长期使用,是使用getErrorStream()并处理它的输出。这并非完全无关紧要,因为在使用阻塞读取的单线程程序中,您永远无法知道哪个流将具有数据。您可以在单独的线程中执行阻塞读取,也可以使用NIO以非阻塞方式处理两个输入。

顺便说一句 - 请注意,自Java 1.5以来,Java文档建议ProcessBuilder.start()超过Runtime.exec()

答案 1 :(得分:0)

您不需要查看标准库的内部方法以获得答案。 The docs of InputStream.read()部分指定了

  

如果没有可用的字节,因为流的结尾已经存在   到达时,返回值-1。此方法阻塞直到输入数据   可用,检测到流的末尾,或者是异常   抛出。

也许混淆的关键点是&#34;检测到流的末尾。&#34;这个意味着现在没有更多的字节可供读取 - 这与方法阻止的规范一致,直到数据可用,并且在实践中它会很难解决在很多情况下。相反,当系统接收到某种信号时,将检测到流的末尾,该信号不再能够从其中获得字节。通常,这意味着流已在源端关闭,并且所有字节在目的端从其中排出。

在您的特定情况下,如果外部进程仍在运行,不会向其标准输出写入任何内容,但是关闭其标准输出,则Java(或本机使用者)将永远不会查看该流程的输出结束。