我不确定,但我非常确定我在Oracle Java实现(我可以证实受影响的1.7.0_67和1.8.0_31 )中发现了一个bug (或未记录的特性)。
The Symptom
当管道已满时,写入管道的等待时间可能比管道再次空闲所需的时间长一秒。这个问题的最小示例如下(我已经将此处显示的示例推送到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毫秒)或调用notifyAll()
时,才会中断wait(1000)
,这只会在以下情况下发生:
在awaitSpace()
中的
wait(1000)
之前,正如我们在receivedLast()
中的Question
有没有人有足够的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)
发布于 2015-02-20 06:05:40
这是Piped*Stream
中众所周知的问题,最终的解决方案(对于JDK-8014239)是“不会修复”。
JDK-4545831:PipedInputStream performance problems
当缓冲区为空时,该类阻塞用于读取,当缓冲区满时,阻塞用于写入。它通过调用wait(1000)来阻塞,但是读取器只会被遇到满缓冲区(或等待超时)的写入器唤醒,而写入器只会被遇到空缓冲区(或等待超时)的读取器唤醒。
客户解决方法:在每次读取()/write()之后通知PipedInputStream ()可能会解决问题,但仍会导致性能不佳,因为会进行许多不必要的notify()调用。
JDK-4404700:由于轮询,PipedInputStream太慢(建议使用alt实现)
java.io.PipedInputStream太慢了,因为它会轮询以检查新数据。每秒它都会测试是否有新的数据可用。当数据可用时,它可能会浪费几乎一秒钟的时间。它还有一个无法设置的小缓冲区。我建议考虑下面的PipedInputStream和PipedOutputStream实现,它更简单,速度更快。
BT2:评估
我们应该把它作为merlin和tiger的机会目标。由于提交的代码旨在替换的类的年龄,在使用它时可能会涉及兼容性问题。
JDK-8014239:PipedInputStream在接收时不通知等待的读者
当读/写PipedInputStream/PipedOutputStream对时,当新数据被写入PipedOutputStream时,read()恰好阻塞一秒钟。这样做的原因是PipedInputStream只在接收()期间缓冲区被填满时唤醒等待的读取器。解决方案非常简单,在PipedInputStream中的两个receive()方法的末尾添加一个notifyAll()。
目前还不清楚大多数现实生活中的场景将如何从拟议的更改中受益。每次写入通知可能会导致不必要的编写器停顿。这就违背了管道的主要目的之一--将读取器与写入器和缓冲时间解耦。PipedInputStream/PipedWriter API为我们提供了一种灵活的方式来控制我们希望读者收到新数据通知的频率。即flush()。在适当的时候调用flush()可以控制延迟和吞吐量。
https://stackoverflow.com/questions/28617175
复制相似问题