我有一个二进制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
。
3条答案
按热度按时间jutyujz01#
您不需要查看标准库的内部方法来获得答案,
InputStream.read()
的文档部分地说明了如果由于已到达流的结尾而没有字节可用,则返回值-1。此方法将一直阻塞,直到输入数据可用、检测到流的结尾或引发异常。
也许混淆的关键点是“检测到流的结尾”的含义。这 * 并不 * 意味着现在没有更多的字节可供读取--这将与方法阻塞直到数据可用的规范不一致,并且在实践中,它在许多情况下的效果很差。当系统接收到某种表示不再有字节可从流中获得的信号时,检测到流的结束。通常,这意味着流在源端已被关闭,并且在目的地端从流中排出所有字节。
在您的特定情况下,如果外部进程保持运行,不向其标准输出进一步写入任何内容,但不 * 关闭 * 其标准输出,则Java(或本机使用者)将永远不会在该进程的输出上看到流结束。
pokxtpni2#
您的
InputStream
正在等待进程提供另一个字节,或者关闭流(通过完成)。进程可能不执行这两项操作的一个原因是,它被写入
stderr
,并且在该写入时阻塞。您的shell和Java使用的环境之间可能存在差异,这意味着命令将导致输出到stderr(例如,程序不在
$PATH
中;您没有足够的权限;您的CWD不是您所期望的,等等。)一种快速而粗略的方法是将
stderr
重定向到stdout
--或者使用ProcessBuilder.redirectErrorStream()
,或者在UNIX端使用2>&1
。从长远来看,处理它的干净方法是使用
getErrorStream()
并处理它的输出。这并不是微不足道的,因为在使用阻塞读取的单线程程序中,您永远不知道哪个流将具有数据。您可以在单独的线程中执行阻塞读取,或者使用NIO以非阻塞方式处理两个输入。顺便说一句,请注意,从Java 1.5开始,Java文档建议
ProcessBuilder.start()
优于Runtime.exec()
。58wvjzkj3#
Linux下有five different type IO model,分别是
java.io.FileInputStream.readBytes()使用第一种类型,阻塞IO。应用程序等待内核返回数据。