Я нашел ошибку в java.io.PipedInputStream?
Я не уверен, но я вполне уверен, что обнаружил ошибку (или не документированную функцию) в реализации Oracle Java (1.7.0_67 и 1.8.0_31 я мог проверить, как это повлияло).
Симптом
Когда труба заполнена, запись в трубу может подождать до одной секунды дольше, чем необходимо, чтобы труба снова стала свободной. Ниже приведен минимальный пример проблемы (я привел пример, показанный здесь, в репозиторий в 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 мс). Это вызвано реализацией PipedInputStream
в Oracle Java 1.7.0_67, которая выглядит следующим образом:
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)
, как мы можем видеть в фрагменте выше
- В
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)
Ответы
Ответ 1
Это хорошо известная проблема в Piped*Stream
, а окончательное разрешение (для JDK-8014239) - "Не исправить".
JDK-4545831: Проблемы с производительностью PipedInputStream
Блоки классов для чтения, когда буфер пустой и блоки для записи, тогда буфер заполнен. Он блокируется вызывающее ожидание (1000), однако читателю будет только разбудить писатель, который встречает полный буфер (или время ожидания), и писатель будет только пробуждается читателем, который сталкивается с пустым буфером (или временем ожидания выход).
Обходное решение для клиента: уведомить() о том, что PipedInputStream после каждого read()/write(), вероятно, решит проблему, но все же приводит к субоптимальная производительность, так как многие ненужные вызовы notify() сделал.
JDK-4404700: PipedInputStream слишком медленный из-за опроса (предлагается реализация alt)
java.io.PipedInputStream слишком медленный, потому что он опрос проверяет наличие новых данных. Каждую секунду он проверяет, доступны ли новые данные. Когда доступны данные потенциально тратит почти секунду. У него также есть неустановившийся небольшой буфер. Я предлагаю рассмотреть следующую реализацию как PipedInputStream, так и PipedOutputStream, который проще и быстрее.
ВТ2: ОЦЕНКА
Мы должны держать это вокруг как цель возможностей для мерлина и тигра. Из-за возраста классов представленный код предназначен для могут возникнуть проблемы совместимости, связанные с ее использованием.
JDK-8014239: PipedInputStream не уведомляет ожидающих читателей при получении
При чтении/записи из пары PipedInputStream/PipedOutputStream, read() блокируется ровно на одну секунду, когда новые данные записываются в PipedOutputStream. Причиной этого является то, что PipedInputStream только пробуждает ожидающих читателей, когда во время приема() буфер заполняется. Решение очень просто, добавьте notifyAll() в конце обоих методов receive() в PipedInputStream.
Неясно, как большинство реальных сценариев выиграют от предлагаемое изменение. Уведомления на запись могут привести к ненужной записи киоски. Таким образом, побеждая одну из основных целей развязки труб - время читателей от писателей и буферизации. API PipedInputStream/PipedWriter дает нам гибкий способ контролировать, как часто мы хотели бы, чтобы читатель был уведомлен о новых данных. А именно, flush(). призвание flush() в нужное время, мы можем контролировать задержку и пропускную способность.