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

我在java.io.PipedInputStream中发现错误了吗?,java,Java,我不确定,但我非常确定我在Oracle Java实现中发现了一个bug(或一个未记录的特性)(1.7.0_67和1.8.0_31我可以验证是否受到影响) 症状 当管道已满时,对管道的写入可能比管道再次变为空闲所需的时间长一秒。这个问题的一个最简单的例子如下(我将这里显示的示例推到): pi和pos分别连接到PipedInputStream和PipedOutputStream实例logA和logB是辅助函数,它们输出线程名称(A或B)、以毫秒为单位的时间戳和消息。结果如下: 0 A: F

我不确定,但我非常确定我在Oracle Java实现中发现了一个bug(或一个未记录的特性)(1.7.0_67和1.8.0_31我可以验证是否受到影响)

症状

当管道已满时,对管道的写入可能比管道再次变为空闲所需的时间长一秒。这个问题的一个最简单的例子如下(我将这里显示的示例推到):

pi
pos
分别连接到
PipedInputStream
PipedOutputStream
实例
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.
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();
        }
    }
}
可以看到,在
B:Done
A:Done
之间有一秒钟(1000毫秒)。这是由于在Oracle Java 1.7.0_67中实现了
PipedInputStream
,如下所示:

     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.
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()
来中断,这仅在以下情况下发生:

  • waitspace()
    中,在
    wait(1000)
    之前,我们可以在上面的代码片段中看到
  • receivedLast()
    中,当流关闭时调用该函数(此处不适用)
  • 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)

这是管道*流中众所周知的问题,最终解决方案(for)是“不会修复”

PipedInputStream性能问题

当缓冲区被激活时,该类将阻止读取 空和块进行写入,则缓冲区已满。路过 然而,调用wait(1000),读者只会被 遇到已满的缓冲区(或等待超时),写入程序只会 被遇到空缓冲区(或等待时间)的读者唤醒 出局)

客户的解决方法:在每次读取()/write()后通知()调用PipedInputStream可能会解决问题,但仍然会导致 性能不理想,因为正在执行许多不必要的notify()调用 制造


由于轮询,PipedInputStream太慢(建议使用alt实现)

java.io.PipedInputStream太慢,因为它轮询以检查新数据。 每秒钟它都会测试是否有新数据可用。当数据可用时 可能会浪费几乎一秒钟的时间。它还有一个不稳定的小缓冲区。 我建议考虑以下两个实施方案: PipedOutputStream,更简单、更快

BT2:评估

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


PipedInputStream在接收时不通知等待的读卡器

当从PipedInputStream/PipeDoutpStream对读取/写入时,当新数据写入PipeDoutpStream时,read()会精确阻塞一秒钟。原因是PipedInputStream仅在receive()期间缓冲区被填满时唤醒等待的读卡器。 解决方案非常简单,在PipedInputStream中的两个receive()方法的末尾添加notifyAll()

目前还不清楚大多数现实生活场景将如何从 提议的改变。每次写入通知可能导致不必要的写入程序 摊位。这样就破坏了管道的主要用途之一——时间解耦 来自作者和缓冲区的读者。 PipedInputStream/PipedWriter API为我们提供了一种灵活的方法来控制频率 我们希望读者收到有关新数据的通知。即flush()。使命感 flush()在正确的时间我们可以控制延迟和吞吐量


是的,这听起来像是一个bug。在寻找现有的bug报告时,我故意不考虑OpenJDK,因为我认为这是一个不同的JVM实现。它们是否共享相同的错误状态,即“不会修复”是否也适用于Oracle的实现?请同时查看答案,感谢您链接到错误-这完全回答了我的问题。还感谢您提供有关OpenJDK与Oracle JDK的说明。我们通过子类化PipedOutStream并在调用super.write()后重写write方法来调用flush(),克服了这一问题