我不确定,但我很确定我在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.");
}
pis
和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.
可以看出,B: Done
和A: 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();
}
}
}
只有在达到超时(1000毫秒,如上所示)或调用wait(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)
答案 0 :(得分:13)
这是[{1}}中众所周知的问题,最终解决方案(针对JDK-8014239)未得到解决#34;
JDK-4545831: PipedInputStream性能问题
缓冲区时读取的类块 空和写入块然后缓冲区已满。它会阻挡 呼叫等待(1000),然而读者只会被作家吵醒 遇到一个完整的缓冲区(或等待超时),只有一个作家 被遇到空缓冲区的读者(或等待时间)吵醒 出)。
客户解决方法:在每次read()/ write()之后Notify()通知PipedInputStream可能会解决问题,但仍会导致 由于许多不必要的notify()调用正在进行,因此性能欠佳 制成。
JDK-4404700: PipedInputStream由于轮询而太慢(建议使用alt实现)
java.io.PipedInputStream太慢,因为它轮询以检查新数据。 它每秒都会测试新数据是否可用。当数据可用时 可能浪费了近一秒钟。它还有一个不可设置的小缓冲区。 我建议考虑PipedInputStream和的以下实现 PipedOutputStream,更简单,更快。
BT2:评价
我们应该把它作为梅林和老虎的机会目标。由于提交的代码的类的年龄设计 替换,使用它可能存在兼容性问题。
JDK-8014239: PipedInputStream未通知等待读者
当从PipedInputStream / PipedOutputStream对读/写时,当新数据写入PipedOutputStream时,read()会准确地阻塞一秒钟。原因是PipedInputStream只唤醒等待的读者,当在receive()期间填充缓冲区时。 解决方案非常简单,在PipedInputStream中的两个receive()方法的末尾添加一个notifyAll()。
大多数现实生活场景如何从中获益并不明显 建议改变。每次写入通知可能会导致不必要的编写者 摊位。从而打破了管道 - 时间 - 解耦的主要目的之一 来自作家和缓冲的读者。 PipedInputStream / PipedWriter API为我们提供了一种灵活的方式来控制频率 我们希望读者收到有关新数据的通知。即,flush()。调用 flush()在适当的时候我们可以控制延迟和吞吐量。