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