Я не уверен, но я вполне уверен, что обнаружил ошибку (или не документированную функцию) в реализации 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)