001
002 /*
003 * Copyright (C) 2011 Archie L. Cobbs. All rights reserved.
004 *
005 * $Id: TimedWait.java 102 2011-05-12 17:54:35Z archie.cobbs $
006 */
007
008 package org.dellroad.stuff.java;
009
010
011 /**
012 * Utility class for performing timed waits on objects.
013 *
014 * @see Object#wait
015 */
016 public final class TimedWait {
017
018 private TimedWait() {
019 }
020
021 /**
022 * Wait (using {@link Object#wait(long) Object.wait()}) up to a given time limit for some predicate to become true.
023 * This method correctly handles {@link Object#wait spurious wakeups}, restarting the wait loop as necessary.
024 *
025 * <p>
026 * This method assumes that {@code obj} will be notified whent the predicate becomes true and that the current thread
027 * is already synchronized on {@code obj}.
028 *
029 * <p>
030 * This method uses {@link System#nanoTime} instead of {@link System#currentTimeMillis} and so is immune to
031 * adjustments in clock time.
032 *
033 * @param obj object to sleep on; must already be locked
034 * @param timeout wait timeout in milliseconds, or zero for an infinite wait
035 * @param predicate predicate to test
036 * @return true if the predicate became true before the timeout, false if the timeout expired
037 * @throws IllegalArgumentException if {@code timeout} is negative
038 * @throws IllegalMonitorStateException if {@code obj} is not already locked
039 * @throws InterruptedException if the current thread is interrupted
040 */
041 public static boolean wait(Object obj, long timeout, final Predicate predicate) throws InterruptedException {
042
043 // Sanity check timeout value
044 if (timeout < 0)
045 throw new IllegalArgumentException("timeout = " + timeout);
046
047 // Record start time (only if there's a timeout)
048 long startTime = timeout > 0 ? System.nanoTime() : 0;
049
050 // Loop waiting for predicate
051 while (!predicate.test()) {
052
053 // Did the timeout expire?
054 if (timeout < 0)
055 return false;
056
057 // Wait for remaining timeout to be woken up
058 obj.wait(timeout);
059
060 // If there's a timeout, subtract the time we just waited (rounding to the nearest millisecond)
061 if (timeout > 0) {
062 long stopTime = System.nanoTime();
063 timeout -= (stopTime - startTime + 500) / 1000L;
064 if (timeout == 0) // don't convert the last millisecond into an infinite wait
065 timeout = -1;
066 startTime = stopTime;
067 }
068 }
069
070 // Predicate was true
071 return true;
072 }
073 }
074