【问题标题】:Did I find a bug in java.io.PipedInputStream?我在 java.io.PipedInputStream 中发现错误了吗?
【发布时间】:2015-02-19 21:23:08
【问题描述】:

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

症状

当管道已满时,对管道的写入可能会比管道再次空闲所需的时间多等待一秒钟。问题的一个最小示例如下(我已将此处显示的示例推送到a repository on 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 实例。 logAlogB 是辅助函数,它们输出线程名称(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.

可以看到,B: DoneA: Done 之间有一秒(1000 毫秒)。这是由于Oracle Java 1.7.0_67中PipedInputStream的实现导致的,如下:

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) 之前,我们可以在上面的sn-p 中看到
  • receivedLast()中,流关闭时调用(此处不适用)
  • read() 中,但仅当read() 正在等待空缓冲区填满时——此处也不适用

问题

有没有人有足够的 Java 经验告诉我这是否应该是预期的行为? PipedOutputStream.write(...) 使用方法awaitSpace() 来等待空闲空间,他们的合约简单地声明:

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

虽然严格没有违反这一点,但 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)

【问题讨论】:

  • 是的,这听起来像是一个错误。

标签: java


【解决方案1】:

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

JDK-4545831: PipedInputStream 性能问题

当缓冲区被读取时的类块 为空并阻塞写入,则缓冲区已满。它挡住了 调用 wait(1000),但是读者只会被写者唤醒 遇到一个完整的缓冲区(或等待超时)并且写入器只会 被遇到空缓冲区(或等待时间)的读者唤醒 出)。

客户解决方法:在每次 read()/write() 之后通知 PipedInputStream 可能会解决问题,但仍会导致 性能欠佳,因为许多不必要的 notify() 调用正在 制作。


JDK-4404700: PipedInputStream 由于轮询而太慢(建议实现替代方案)

java.io.PipedInputStream 太慢了,因为它轮询检查新数据。 它每秒都会测试是否有新数据可用。当数据可用时 可能会浪费几乎一秒钟。它还有一个不可设置的小缓冲区。 我建议考虑以下 PipedInputStream 和 PipedOutputStream,更简单,速度更快。

BT2:评估

我们应该把它作为梅林和老虎的机会目标。由于类的年龄,提交的代码旨在 替换,使用时可能会涉及到兼容性问题。


JDK-8014239: PipedInputStream 在接收时未通知等待的读者

当从 PipedInputStream/PipedOutputStream 对读取/写入时,read() 会在新数据写入 PipedOutputStream 时恰好阻塞一秒钟。这样做的原因是 PipedInputStream 只唤醒等待的读者,当在 receive() 期间缓冲区被填满时。 解决方法很简单,在 PipedInputStream 的两个 receive() 方法的末尾添加一个 notifyAll()。

目前尚不清楚大多数现实生活场景将如何受益于 提议的改变。每次写入通知可能会导致不必要的写入器 摊位。从而破坏了管道的主要目的之一——时间解耦 来自作家和缓冲的读者。 PipedInputStream/PipedWriter API 为我们提供了一种灵活的方式来控制频率 我们希望读者收到有关新数据的通知。即,flush()。打电话 在合适的时间使用flush(),我们可以控制延迟和吞吐量。

【讨论】:

  • 在寻找现有的错误报告时,我故意不考虑 OpenJDK,因为我认为它是一个不同的 JVM 实现。他们是否共享相同的错误状态,即。 “不会修复”是否也适用于 Oracle 的实施?
  • 感谢您链接到错误 - 这完全回答了我的问题。还要感谢您提供有关 OpenJDK 与 Oracle JDK 的说明。
  • 我们通过继承 PipedOutputStream 并在调用 super.write() 后重写 write 方法来调用 flush() 来克服这个问题
猜你喜欢
  • 1970-01-01
  • 2011-04-01
  • 2021-04-06
  • 1970-01-01
  • 2014-09-12
  • 1970-01-01
  • 2012-08-20
  • 2011-02-11
  • 2021-11-27
相关资源
最近更新 更多