我在java.io.PipedInputStream中发现了一个错误吗?

内容来源于 Stack Overflow,并遵循CC BY-SA 3.0许可协议进行翻译与使用

  • 回答 (1)
  • 关注 (0)
  • 查看 (44)

我不确定,但是我非常肯定,我在Oracle Java实现中发现了一个错误(或一个未记录的功能)(我可以验证为1.7.0_67和1.8.0_31)。

当pipe已满时,写入管道的时间可能会比管道再次变为空闲所需的时间长1秒。这个问题的一个最简单的例子如下(我将这里展示的示例推送到GitHub上的存储库):

private static void threadA() throws IOException, InterruptedException {
  logA("Filling pipe...");
  pos.write(new byte[5]);
  logA("Pipe full. Writing one more byte...");
  pos.write(0);
  logA("Done.");
}

private static void threadB() throws IOException, InterruptedException {
  logB("Sleeping a bit...");
  Thread.sleep(100);
  logB("Making space in pipe...");
  pis.read();
  logB("Done.");
}

pispos分别连接PipedInputStreamPipedOutputStream实例。logA并且logB是辅助函数,它们输出线程名称(A或B),以毫秒为单位的时间戳和消息。输出如下:

     0 A: Filling pipe...
     6 B: Sleeping a bit...
     7 A: Pipe full. Writing one more byte...
   108 B: Making space in pipe...
   109 B: Done.
  1009 A: Done.

如可以看到的,存在之间的一个秒(1000毫秒)B: DoneA: Done。这是由PipedInputStreamOracle Java 1.7.0_67中的实现引起的,如下所示:

private void awaitSpace() throws IOException {
    while (in == out) {
        checkStateForReceive();

        /* full: kick any waiting readers */
        notifyAll();
        try {
            wait(1000);
        } catch (InterruptedException ex) {
            throw new java.io.InterruptedIOException();
        }
    }
}

wait(1000)仅由任一击中超时(1000毫秒,如上所示),或一个呼叫中断notifyAll(),这仅发生在下列情况下:

  • awaitSpace()之前wait(1000),我们可以在上面的代码片段中看到
  • receivedLast()流被关闭时调用(这里不适用)
  • read(),但只有read()等待一个空的缓冲区填满 - 也不适用于此

问题

有没有人有足够的Java经验告诉我这是否应该是有意的行为?该方法awaitSpace()用于PipedOutputStream.write(...)等待空闲空间,他们的合同简单地陈述:

此方法阻塞,直到所有字节写入输出流。

虽然这是严格没有违反,但1秒的等待时间似乎相当长。如果我要解决这个问题(尽量减少或缩短等待时间),我建议notifyAll()在每次阅读结束时插入一个以确保等待的作者得到通知。为了避免额外的同步时间开销,可以使用简单的布尔标志(并且不会影响线程安全性)。

受影响的Java版本

到目前为止,我可以在Java 7和Java 8上验证这一点,下面的版本是精确的:

$ java -version
java version "1.7.0_67"
Java(TM) SE Runtime Environment (build 1.7.0_67-b01)
Java HotSpot(TM) 64-Bit Server VM (build 24.65-b04, mixed mode)

$ java -version
java version "1.8.0_31"
Java(TM) SE Runtime Environment (build 1.8.0_31-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.31-b07, mixed mode)
提问于
用户回答回答于

这是众所周知的问题Piped*Stream和最终解决方案(对于JDK-8014239)是“不会修复”。

JDK-4545831PipedInputStream性能问题 当缓冲区为空时,用于读取的类块将被写入,然后缓冲区已满。它通过调用wait(1000)来阻塞,但是只有遇到完整缓冲区的作者才会唤醒读者(或等待超时),并且只有遇到空缓冲区的读者才会唤醒作者(或等待超时)。 客户解决方法:在每次read()/ write()之后通知()PipedInputStream可能会解决问题,但仍会导致次优性能,因为正在进行许多不必要的notify()调用。

JDK-4404700由于轮询,PipedInputStream速度太慢(建议使用alt实现) java.io.PipedInputStream速度太慢,因为它轮询检查新数据。每秒都会测试新数据是否可用。当数据可用时,它可能浪费几乎一秒钟的时间。它也有一个不可取的小缓冲区。我建议考虑下面的PipedInputStream和PipedOutputStream的实现,它更简单,速度更快。 BT2:评估 我们应该把这一点作为梅林和老虎的机会目标。由于提交的代码旨在替换类的年龄,因此可能存在涉及使用它的兼容性问题。

JDK-8014239PipedInputStream不会通知收到等待的读者 当从PipedInputStream / PipedOutputStream对读取/写入时,当新数据写入PipedOutputStream时,read()会严格阻塞一秒钟。原因是PipedInputStream只会在receive()缓冲区填满期间唤醒等待的读者。解决方案非常简单,在PipedInputStream中的两个receive()方法的末尾添加一个notifyAll()。 大多数现实生活中的场景如何能够从建议的变化中受益,这并不明显。每写入通知可能会导致不必要的作家停滞。从而击败了管道的主要目的之一 - 将读者从作者和缓冲中解耦出来。PipedInputStream / PipedWriter API为我们提供了一种灵活的方式来控制我们希望通知读者新的数据的频率。即,flush()。在正确的时间调用flush(),我们可以控制延迟和吞吐量。

扫码关注云+社区