我在java.io.PipedInputStream中发现错误了吗?
我不确定,但我非常确定我在Oracle Java实现中发现了一个bug(或一个未记录的特性)(1.7.0_67和1.8.0_31我可以验证是否受到影响) 症状 当管道已满时,对管道的写入可能比管道再次变为空闲所需的时间长一秒。这个问题的一个最简单的例子如下(我将这里显示的示例推到):我在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
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()
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(),克服了这一问题