Как использовать TimeUnit.timedWait() без потери наносекундной точности?

Я пытаюсь реализовать Future.get(long, TimeUnit) с точки зрения TimeUnit.timedWait(Object, long).

Непонятно, как использовать 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()?


person Gili    schedule 29.12.2009    source источник
comment
Можете ли вы уточнить условия, при которых вас беспокоят ложные пробуждения? Есть ли что-то в коде, из-за чего трудно понять, кто контролирует объекты Monitor?   -  person Steve B.    schedule 29.12.2009
comment
Я совершенно уверен, кто контролирует объект Монитор. Ложные пробуждения относятся к тому факту, что операция wait() может завершиться, даже если никто не вызывал notify() или истекло время ожидания.   -  person Gili    schedule 29.12.2009


Ответы (3)


Прежде чем ждать, сохраните время, в которое вы хотите тайм-аут.

Прежде чем уведомлять ожидающий поток, установите некоторое общее (синхронизированное) состояние, сигнализирующее о том, что ожидающий поток должен прекратить ожидание, поскольку вычисление завершено.

Когда ваш поток выходит из ожидания по какой-либо причине, он должен проверить общее состояние, чтобы увидеть, должен ли он прекратить ожидание, а также проверить, сколько времени осталось до истечения времени ожидания. Если время ожидания не истекло, а общее состояние не говорит прекратить ожидание, вам следует снова подождать (но с использованием нового более короткого времени ожидания, рассчитанного из текущего времени).

person Mark Byers    schedule 29.12.2009
comment
Непонятно, как сохранить время, когда вы хотите тайм-аут, не теряя при этом наносекундный компонент TimeUnit. Если все просто отказываются от наносекундного компонента, то какой смысл в TimeUnit, даже поддерживающем наносекунды и предлагающем TimeUnit.timedWait()? - person Gili; 29.12.2009
comment
Ну, я не могу сказать, почему они решили разрешить наносекундную точность. На практике я сомневаюсь, что вы все равно получите такую ​​точность от обычной операционной системы. И если вам действительно нужна такая точность, то использование языка со сборщиком мусора звучит как очень плохая идея. - person Mark Byers; 30.12.2009
comment
Я обновил вопрос, чтобы он был более ориентирован на TimeUnit, но я проголосовал за отличный совет Object.wait(). - person Gili; 30.12.2009

Ответ на ваш вопрос содержится в Object.wait(long) спецификация:

Поток также может проснуться без уведомления, прерывания или тайм-аута, так называемое ложное пробуждение. Хотя на практике это происходит редко, приложения должны защищаться от этого, проверяя условие, которое должно было вызвать пробуждение потока, и продолжая ждать, если условие не выполняется. Другими словами, ожидание всегда должно происходить в циклах, подобных этому:

 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, 2000). Уэсли, 2001).

person LeWoody    schedule 30.12.2009
comment
Вам нужно не забыть уменьшить время ожидания во второй раз, когда вы ждете, так как часть времени уже прошла. - person Mark Byers; 30.12.2009

CountDownLatch кажется самым простым способ реализовать это:

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 не подвержен ложным пробуждениям (поскольку перед возвратом он может внутренне проверить состояние защелки).

person Gili    schedule 30.12.2009