Как использовать TimeUnit.timedWait() без потери наносекундной точности?
-
21-09-2019 - |
Вопрос
Я пытаюсь реализовать Future.get(долго, временная единица) с точки зрения TimeUnit.timedWait(Объект, длинный).
Непонятно, как использовать TimeUnit.timedWait(Object, long)
таким образом, который обрабатывает ложные пробуждения без потери наносекундной составляющей TimeUnit.Обычно вы бы сделали что-то вроде этого:
public V get(long timeout, TimeUnit unit)
{
long expirationTime = new Date().getTime() + unit.toMillis(timeout);
while (!condition)
{
long timeLeft = expirationTime - new Date().getTime();
if (timeLeft <= 0)
throw new TimeoutException();
unit.timedWait(object, timeLeft);
}
}
но вы теряете наносекундную составляющую.Если все просто отбрасывают наносекундную составляющую, то в чем смысл TimeUnit
даже поддерживая наносекунды и предлагая TimeUnit.timedWait()
?
Решение 3
Обратный отсчет времени кажется, это самый простой способ реализовать это:
public class MutableFuture<T> implements Future<T>
{
private final CountDownLatch done = new CountDownLatch(1);
private T value;
private Throwable throwable;
public synchronized boolean isDone()
{
return done.getCount() == 0;
}
public synchronized T get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException
{
if (!done.await(timeout, unit))
throw new TimeoutException();
if (throwable != null)
throw new ExecutionException(throwable);
return value;
}
// remaining methods left as exercise to the reader :)
}
CountDownLatch не подвержен ложные пробуждения (потому что он может внутренне проверить состояние защелки перед возвратом).
Другие советы
Прежде чем ждать, запишите время, в которое вы хотите установить тайм-аут.
Прежде чем уведомлять ожидающий поток, установите некоторое общее (синхронизированное) состояние, сигнализирующее о том, что ожидающий поток должен прекратить ожидание, поскольку вычисление завершено.
Когда ваш поток по какой-либо причине выходит из режима ожидания, он должен проверить общее состояние, чтобы увидеть, следует ли ему прекратить ожидание, и он также должен проверить, сколько времени осталось до истечения тайм-аута.Если тайм-аут не истек, и общее состояние сообщает, что не нужно прекращать ожидание, тогда вам следует подождать еще раз (но используя новый более короткий тайм-аут, рассчитанный исходя из текущего времени).
Ответ на ваш вопрос заключается в Объект. ждать (долго) спецификация:
Поток также может просыпаться без уведомления, прерывания или тайм-аута, так называемого ложного пробуждения.Хотя на практике это происходит редко, приложения должны защищаться от этого, проверяя условие, которое должно было вызвать пробуждение потока, и продолжая ждать, если условие не выполняется.Другими словами, ожидание всегда должно происходить в циклах, подобных этому:
synchronized (obj) {
while (<condition does not hold>)
obj.wait(timeout);
... // Perform action appropriate to condition
}
(Для получения дополнительной информации по этой теме смотрите Раздел 3.2.3 в книге Дуга Ли "Параллельное программирование на Java (второе издание)" (Addison-Wesley, 2000) или пункт 50 в книге Джошуа Блоха "Эффективное руководство по языку программирования Java" (Addison-Wesley, 2001).