Chapter 14. Waiting for the Full Timeout

IN THIS CHAPTER

In Java, two threads can communicate with each other through the wait-notify mechanism (see Chapter 8, "Inter-thread Communication"). When a thread invokes one of the wait() methods of Object, an optional timeout value can be specified. The thread does not return from the wait(long msTimeout) call until it is notified by another thread, the specified millisecond timeout has elapsed, or it is interrupted. It is obvious when it has been interrupted as an InterruptedException is thrown. But other than that, wait() does not return any information as to whether it was notified or just timed out.

There will be times when you'll want to write code that waits up to a specified amount of time for a condition to be met. The waiting thread might be notified many times before the condition is met. Imagine that you want a thread to wait for an integer value to increase from 5 to a minimum of 20. Every time the value is changed by another thread, the waiting thread is notified. The notification doesn't necessarily mean that value being waited for has been met, but simply that the value has been changed. The thread waiting for the condition to be met will return from wait(long msTimeout) either because the timeout has expired, or because it has been notified. In this chapter, I'll show you a technique for differentiating between these two possibilities to ensure that the full timeout elapses when required.

Accidentally Returning Early

In EarlyReturn, shown in Listing 14.1, one thread blocks waiting for the internal value to reach a specified minimum while a second thread changes the value. Every time the second thread changes the value, it notifies any waiting threads. The first thread is waiting (up to the timeout) for a particular value to be reached. In this example, the first thread will accidentally return before the value is reached and before the timeout occurs.

Example 14.1. EarlyReturn.java—An Example of an Accidental Early Return

 1: public class EarlyReturn extends Object {
 2:     private volatile int value;
 3:
 4:     public EarlyReturn(int initialValue) {
 5:         value = initialValue;
 6:     }
 7:
 8:     public synchronized void setValue(int newValue) {
 9:         if ( value != newValue ) {
10:             value = newValue;
11:             notifyAll();
12:         }
13:     }
14:
15:     public synchronized boolean waitUntilAtLeast(
16:                 int minValue,
17:                 long msTimeout
18:             ) throws InterruptedException {
19:
20:         System.out.println("entering waitUntilAtLeast() - " +
21:                 "value=" + value +
22:                 ",minValue=" + minValue);
23:
24:         if ( value < minValue ) {
25:             wait(msTimeout);
26:         }
27:
28:         System.out.println("leaving waitUntilAtLeast() - " +
29:                 "value=" + value +
30:                 ",minValue=" + minValue);
31:
32:         // May have timed out, or may have met value,
33:         // calc return value.
34:         return ( value >= minValue );
35:     }
36: 
37:     public static void main(String[] args) {
38:         try {
39:             final EarlyReturn er = new EarlyReturn(0);
40:
41:             Runnable r = new Runnable() {
42:                     public void run() {
43:                         try {
44:                             Thread.sleep(1500);
45:                             er.setValue(2);
46:                             Thread.sleep(500);
47:                             er.setValue(3);
48:                             Thread.sleep(500);
49:                             er.setValue(4);
50:                         } catch ( Exception x ) {
51:                             x.printStackTrace();
52:                         }
53:                     }
54:                 };
55:
56:             Thread t = new Thread(r);
57:             t.start();
58:
59:             System.out.println(
60:                     "about to: waitUntilAtLeast(5, 3000)");
61:             long startTime = System.currentTimeMillis();
62:             boolean retVal = er.waitUntilAtLeast(5, 3000);
63:             long elapsedTime =
64:                     System.currentTimeMillis() - startTime;
65:
66:             System.out.println("after " + elapsedTime +
67:                     " ms, retVal=" + retVal);
68:         } catch ( InterruptedException ix ) {
69:             ix.printStackTrace();
70:         }
71:     }
72: }

EarlyReturn holds the last value set in the private member variable value (line 2). The constructor (lines 4–6) simply sets value to some initial state. The setValue() method (lines 8–13) is synchronized so it blocks until it can acquire the lock on this (the EarlyReturn instance). After the lock is acquired, setValue() checks to see if newValue is really new (line 9) to avoid unnecessary notifications. If it is different, setValue() sets value (line 10) and then notifies any and all threads waiting on this object for a change (line 11).

The waitUntilAtLeast() method (lines 15–35) is used by threads that want to block—up to a specified amount of time—waiting for value to increase to the minimum specified. Any number of threads can be blocked simultaneously inside waitUntilAtLeast() as notifyAll() is used inside setValue() to be sure that all waiters are notified of the change. If the minimum value has not yet been met (line 24), the thread waits for up to msTimeout milliseconds for it to increase (line 25). Regardless of whether the minimum was achieved or the wait timed out, the boolean returned is calculated based on the current setting of value. If value is at least minValue, true is returned. Otherwise, false is returned to indicate a timeout.

The main() method (lines 37–71) is used to create a new thread to call setValue(), while the main thread continues to block on waitUntilAtLeast(). A new EarlyReturn object is created with an initial value of 0 (line 39). It is final so that it can be accessed from the anonymous inner class. A new Runnable is declared (lines 41–54) to alternately sleep and call setValue() with new values (lines 44–49). Next, the Thread for this Runnable is created and started (lines 56–57).

The main thread then takes note of the current time to be able to measure how long waitUntilAtLeast() blocks (line 61). The waitUntilAtLeast() method is invoked with a minimum value to wait for of 5, and a maximum time to wait of three seconds (line 60). The boolean returned from waitUntilAtLeast() is captured into retVal. The elapsed time that the thread was blocked inside waitUntilAtLeast() is calculated (lines 63–64) and printed along with retVal (lines 66–67).

When EarlyReturn is run, output much like the following is produced (your output should almost match except for slightly different times):

about to: waitUntilAtLeast(5, 3000)
entering waitUntilAtLeast() - value=0,minValue=5
leaving waitUntilAtLeast() - value=2,minValue=5
after 1370 ms, retVal=false

The problem with this is that waitUntilAtLeast() returns before the minimum value of 5 is reached and before the timeout of 3000 milliseconds has elapsed. This isn't what waitUntilAtLeast() was designed to do!

What's going on here is that after the new thread sleeps for 1500 milliseconds, it invokes er.setValue(2) (line 45). Inside setValue(), the notifyAll() (line 11) causes any and all waiting threads to return from wait(). By the time this happens, the main thread has been waiting for a little more than one second (line 25). The main thread returns from wait(), prints the current values, and calculates the boolean to return. In this case, false is returned because it was waiting for a minimum of 5 and value was only set to 2. What is needed here is a way to tell if wait() returned because the timeout was reached, or if it returned because it was notified. If it returned because it was notified, a check needs to be added to see if minValue has been met yet. If it hasn't been met, wait() should be invoked again. If it returned because it timed out, no more waiting should be done.

Determining If wait() Should Be Invoked Again

When wait() returns, it could be because the timeout was reached or because it was notified. In fact, in the EarlyReturn example there are four ways that a thread blocked on wait() becomes unblocked. They are listed in Table 14.1.

Table 14.1. What To Do After wait() Returns

State Action
InterruptedException thrown Pass on InterruptedException
Timed out Return false right away
Notified, minimum is met Return true right away
Notified, minimum not met Calculate time remaining, wait again

The InterruptedException is obvious and should be allowed to propagate up to indicate that the wait() call was terminated early—probably because someone wants this thread to clean up and die as soon as possible. Other than that, wait() is declared to return void so there is no direct indication as to whether the thread timed out or was notified. If it was notified, it's possible that the condition has not yet been met and more time is available to wait again. When the thread returns from wait(), two calculations need to be done: one to see if the condition has been met, and another to see how much more time remains before the timeout value has been reached. If the condition has not been met and there is some time remaining, the thread should wait again. For this second call to wait(), a revised (shorter) timeout value needs to be passed.

EarlyReturnFix, shown in Listing 14.2, is basically the same as EarlyReturn except with a new waitUntilAtLeast() method (lines 15–42). It deals with all of the issues listed in Table 14.1.

Example 14.2. EarlyReturnFix.java—Waiting for the Full Timeout

 1: public class EarlyReturnFix extends Object {
 2:     private volatile int value;
 3:
 4:     public EarlyReturnFix(int initialValue) {
 5:         value = initialValue;
 6:     }
 7:
 8:     public synchronized void setValue(int newValue) {
 9:         if ( value != newValue ) {
10:             value = newValue;
11:             notifyAll();
12:         }
13:     }
14:
15:     public synchronized boolean waitUntilAtLeast(
16:                 int minValue,
17:                 long msTimeout
18:             ) throws InterruptedException {
19:
20:         System.out.println("entering waitUntilAtLeast() - " +
21:                 "value=" + value + ",minValue=" + minValue);
22:
23:         long endTime = System.currentTimeMillis() + msTimeout;
24:         long msRemaining = msTimeout;
25:
26:         while ( ( value < minValue ) && ( msRemaining > 0L ) ) {
27:             System.out.println("in waitUntilAtLeast() - " +
28:                     "about to: wait(" + msRemaining + ")");
29:             wait(msRemaining);
30:             msRemaining = endTime - System.currentTimeMillis();
31:             System.out.println("in waitUntilAtLeast() - " +
32:                     "back from wait(), new msRemaining=" +
33:                     msRemaining);
34:         }
35:
36:         System.out.println("leaving waitUntilAtLeast() - " +
37:                 "value=" + value + ",minValue=" + minValue);
38: 
39:         // May have timed out, or may have met value,
40:         // calc return value.
41:         return ( value >= minValue );
42:     }
43:
44:     public static void main(String[] args) {
45:         try {
46:             final EarlyReturnFix er = new EarlyReturnFix(0);
47:
48:             Runnable r = new Runnable() {
49:                     public void run() {
50:                         try {
51:                             Thread.sleep(1500);
52:                             er.setValue(2);
53:                             Thread.sleep(500);
54:                             er.setValue(3);
55:                             Thread.sleep(500);
56:                             er.setValue(4);
57:                         } catch ( Exception x ) {
58:                             x.printStackTrace();
59:                         }
60:                     }
61:                 };
62:
63:             Thread t = new Thread(r);
64:             t.start();
65:
66:             System.out.println(
67:                     "about to: waitUntilAtLeast(5, 3000)");
68:             long startTime = System.currentTimeMillis();
69:             boolean retVal = er.waitUntilAtLeast(5, 3000);
70:             long elapsedTime =
71:                     System.currentTimeMillis() - startTime;
72:
73:             System.out.println("after " + elapsedTime +
74:                     " ms, retVal=" + retVal);
75:         } catch ( InterruptedException ix ) {
76:             ix.printStackTrace();
77:         }
78:     }
79: }

The waitUntilAtLeast() method declares that it might throw an InterruptedException (line 18) if one occurs while it is blocked waiting. This way, no action is assumed if the thread is interrupted, but the method passes on the exception to signal that it did not complete normally.

Inside waitUntilAtLeast(), the current time is added to the timeout value specified to determine endTime (line 23). This time limit will be used to determine if the thread should wait again. The number of milliseconds left before endTime is held in the local variable msRemaining. It is initially the full timeout value (line 24), but is recalculated each time wait() returns (line 30).

The while loop is entered only if the minimum value has not already been met and a positive timeout value was passed in (line 26). Inside the loop, after printing an informational message, the thread waits for the current msRemaining (line 29). Regardless of whether it returns from wait() because it was notified or because it timed out, a new msRemaining is calculated based on the difference between the time limit and the current system clock time (line 30).

After another informational message is printed, the while loop expression is re-evaluated (line 26). If the minimum value has been reached, the body of the while loop will not be reexecuted. If the minimum value has still not been met, either the thread was notified early, or the wait() timed out. If the wait() timed out, msRemaining should be less than or equal to 0, and the body of the loop will not be re-executed. If it was early notification, the body of the loop will be re-executed.

Caution

It is important to be careful that 0 is not accidentally passed to wait(). A timeout of 0 indicates that the wait() should never time out. Line 26 in the EarlyReturnFix example checks that the new timeout value is greater than 0.

When the while is done (or if it was skipped altogether), an informational message is printed (lines 36–37). The boolean value returned is calculated based on whether or not the minimum was reached (line 41). The waitUntilAtLeast() method can return for many reasons:

  • A negative or zero timeout was specified.

  • The waiting exceeded the limit specified by the timeout.

  • The minimum had been met or exceeded before the method was invoked.

  • The minimum was reached before the timeout.

Regardless of why waitUntilAtLeast() is returning, the return value is true if the minimum is currently met, and is false otherwise.

Listing 14.3 shows possible output from running EarlyReturnFix. Your output is likely to differ slightly on the exact millisecond figures.

Example 14.3. Output from a Particular Run of EarlyReturnFix (with Line Numbers Added)

 1: about to: waitUntilAtLeast(5, 3000)
 2: entering waitUntilAtLeast() - value=0,minValue=5
 3: in waitUntilAtLeast() - about to: wait(3000)
 4: in waitUntilAtLeast() - back from wait(), new msRemaining=1570
 5: in waitUntilAtLeast() - about to: wait(1570)
 6: in waitUntilAtLeast() - back from wait(), new msRemaining=1080
 7: in waitUntilAtLeast() - about to: wait(1080)
 8: in waitUntilAtLeast() - back from wait(), new msRemaining=590
 9: in waitUntilAtLeast() - about to: wait(590)
10: in waitUntilAtLeast() - back from wait(), new msRemaining=-20
11: leaving waitUntilAtLeast() - value=4,minValue=5
12: after 3020 ms, retVal=false

Each time through the loop and just before the wait(), the number of milliseconds passed to wait() is displayed (output lines 3, 5, 7, and 9). The "main" thread keeps calling setValue() with a new value, but it never gets up to 5. Each time setValue() is called, the waiting thread is notified, returns from wait(), recalculates the number of milliseconds remaining, and determines if it should loop again. Finally, msRemaining is non-positive, and the loop breaks (line 10). The minimum has not been met in 3020 milliseconds, so false is returned (line 12).

The next section shows a more complete example that allows a timeout value of 0 to be passed in and removes all the clutter of the main() method.

A General-Purpose Wait-Until Pattern

In this section, I'll show you a more complete example that can be used as a model and expanded to fit your real-world needs when a wait-until scenario is needed. The main enhancement is to allow a timeout of 0 to be passed in just as wait() allows.

On Object, a call to

public final void wait() throws InterruptedException

behaves as if 0 was passed into

public final void wait(long timeout) throws InterruptedException

in the sense that it waits until notified, regardless of how much real time elapses. FullWait, shown in Listing 14.4, expands upon EarlyNotifyFix to allow msTimeout to be 0 when invoking waitUntilAtLeast() to indicate that the thread should not return until the minimum is reached—regardless of how much time elapses.

Example 14.4. FullWait.java—A Good Base Design Pattern to Use for Wait-Until Situations

 1: public class FullWait extends Object {
 2:     private volatile int value;
 3:
 4:     public FullWait(int initialValue) {
 5:         value = initialValue;
 6:     }
 7:
 8:     public synchronized void setValue(int newValue) {
 9:         if ( value != newValue ) {
10:             value = newValue;
11:             notifyAll();
12:         }
13:     }
14:
15:     public synchronized boolean waitUntilAtLeast(
16:                 int minValue,
17:                 long msTimeout
18:             ) throws InterruptedException {
19:
20:         if ( msTimeout == 0L ) {
21:             while ( value < minValue ) {
22:                 wait();  // wait indefinitely until notified
23:             }
24:
25:             // condition has finally been met
26:             return true;
27:         } 
28:
29:         // only wait for the specified amount of time
30:         long endTime = System.currentTimeMillis() + msTimeout;
31:         long msRemaining = msTimeout;
32:
33:         while ( ( value < minValue ) && ( msRemaining > 0L ) ) {
34:             wait(msRemaining);
35:             msRemaining = endTime - System.currentTimeMillis();
36:         }
37: 
38:         // May have timed out, or may have met value,
39:         // calc return value.
40:         return ( value >= minValue );
41:     }
42:
43:     public String toString() {
44:         return getClass().getName() + "[value=" + value + "]";
45:     }
46: }

FullWait is based upon EarlyReturnFix with the main() method and the informational messages removed. The waitUntilAtLeast() method (lines 15–41) has been expanded to support a timeout of 0.

If msTimeout is equal to 0 (line 20), the while loop continues to wait() until the minimum value has been met (lines 21–23). If the minimum was met before waitUntilAtLeast() was invoked, the while loop's body is never executed, and true is returned. Otherwise, the body of the while loop is executed and the thread waits until notified (line 22). Every time setValue() is called with a new value, any and all waiting threads are notified—regardless of what value was set. This means that the wait() (line 22) might be notified before the minimum is met. If this happens, the while loop will simply execute again, causing the thread to go into another wait-until-notified state. If the minimum is ever met, the while loop will terminate and true will be returned (line 26).

If msTimeout is non-zero, waitUntilAtLeast() will behave exactly as it did in EarlyReturnFix (see detailed description earlier). It will continually wait until either the timeout has been reached, or the minimum has been met (lines 30–40).

FullWaitMain in Listing 14.5 constructs a FullWait and demonstrates different scenarios.

Example 14.5. FullWaitMain.java—Code to Demonstrate FullWait

 1: public class FullWaitMain extends Object {
 2:     private FullWait fullwait;
 3:     private Thread internalThread;
 4:     private volatile boolean noStopRequested;
 5:
 6:     public FullWaitMain(FullWait fw) {
 7:         fullwait = fw;
 8:
 9:         noStopRequested = true;
10:         Runnable r = new Runnable() {
11:                 public void run() {
12:                     try {
13:                         runWork();
14:                     } catch ( Exception x ) {
15:                         x.printStackTrace();
16:                     }
17:                 }
18:             };
19:
20:         internalThread = new Thread(r);
21:         internalThread.start();
22:     }
23:
24:     private void runWork() {
25:         int count = 6;
26:
27:         while ( noStopRequested ) {
28:             fullwait.setValue(count);
29:             System.out.println("just set value to " + count);
30:             count++;
31:
32:             try {
33:                 Thread.sleep(1000);
34:             } catch ( InterruptedException x ) {
35:                 // reassert interrupt
36:                 Thread.currentThread().interrupt();
37:             }
38:         }
39:     }
40: 
41:     public void stopRequest() {
42:         noStopRequested = false;
43:         internalThread.interrupt();
44:     }
45:
46:     public boolean isAlive() {
47:         return internalThread.isAlive();
48:     }
49:
50:     public static void waitfor(FullWait fw, int val, long limit)
51:                 throws InterruptedException {
52:
53:         System.out.println("about to waitUntilAtLeast(" +
54:                 val + ", " + limit + ") ... ");
55:
56:         long startTime = System.currentTimeMillis();
57:         boolean retVal = fw.waitUntilAtLeast(val, limit);
58:         long endTime = System.currentTimeMillis();
59:
60:         System.out.println("waited for " +
61:                 ( endTime - startTime ) +
62:                 " ms, retVal=" + retVal + "
---------------");
63:     }
64:
65:     public static void main(String[] args) {
66:         try {
67:             FullWait fw = new FullWait(5);
68:             FullWaitMain fwm = new FullWaitMain(fw);
69:
70:             Thread.sleep(500);
71:
72:             // should return true before 10 seconds
73:             waitfor(fw, 10, 10000L);
74:
75:             // should return true right away --already >= 6
76:             waitfor(fw, 6, 5000L);
77:
78:             // should return true right away
79:             //   --already >= 6 (negative time ignored)
80:             waitfor(fw, 6, -1000L);
81: 
82:             // should return false right away --not there
83:             // yet & negative time
84:             waitfor(fw, 15, -1000L);
85:
86:             // should return false after 5 seconds
87:             waitfor(fw, 999, 5000L);
88:
89:             // should eventually return true
90:             waitfor(fw, 20, 0L);
91:
92:             fwm.stopRequest();
93:         } catch ( InterruptedException x ) {
94:             System.err.println("*unexpectedly* interrupted " +
95:                     "somewhere in main()");
96:         }
97:     }
98: }

In main(), a FullWait object is constructed with an initial value of 5 (line 67). This object is passed as a parameter to the constructor of FullWaitMain (line 68). The main thread then sleeps to let the internal thread get up and going (line 70). This sleep is not a critical step, but helps ensure that the other thread has a chance to run for a little bit (about half a second) before the waiting begins.

The constructor for FullWaitMain (lines 6–22) stores the reference to the FullWait object, starts the internal thread running, and then returns. The internal thread invokes runWork() (lines 24–39). In the while loop inside runWork(), the thread invokes the setValue() method on the FullWait object (line 28). Initially, it passes 6 to setValue() and then sleeps for one second (line 33). Each time through the while loop, the value passed is incremented by 1 (line 30). The internal thread continues to increment the value once per second until a stop request is received.

Back in main(), several calls to the static method waitFor() are made. The waitFor() method (lines 50–63) is used to call waitUntilAtLeast() on the FullWait object. Each time informational messages are printed, the time spent inside waitUntilAtLeast() is measured, and the return value is captured.

The first waitFor() call (line 73) waits for up to 10 seconds for the value to reach 10. The value should be at about 6 when this is called, and because it increments once each second, it should easily reach a minimum value of 10 before the timeout.

The next call (line 76) waits for the value to reach 6, but it is already at least 10, so it returns true right away. The next call (line 80) passes a negative timeout value, but because the minimum value passed is 6 and it has already been reached, it should return true right away and ignore the negative timeout. The next call (line 84) passes in a minimum of 15 (which has not yet been reached) and a negative timeout. The negative timeout causes it to return right away, and because the minimum was not reached, false is returned. The next call (line 87) will only wait five seconds for the value to reach 999. This won't happen in that time, so after five seconds, it returns false. The last call (line 90) specifies a minimum of 20 and a timeout of 0. A timeout of 0 will cause it to wait indefinitely until the minimum value is reached. A few seconds later, 20 is reached, and true is returned.

For cleanup purposes, stopRequest() is called (line 92) to shut down the internal thread. Most of main() is in a try/catch block (lines 66–96) because an InterruptedException might be thrown. In this case, this occurrence is quite unexpected and a message to that effect is printed if it happens (lines 94–95).

Listing 14.6 shows output from a particular run of FullWaitMain. Your times will probably vary from this a bit, but the true and false return values should match exactly for each of the scenarios.

Example 14.6. Sample Output from Running FullWaitMain (Your Output Will Differ)

 1: just set value to 6
 2: about to waitUntilAtLeast(10, 10000) ...
 3: just set value to 7
 4: just set value to 8
 5: just set value to 9
 6: just set value to 10
 7: waited for 3630 ms, retVal=true
 8: ---------------
 9: about to waitUntilAtLeast(6, 5000) ...
10: waited for 0 ms, retVal=true
11: ---------------
12: about to waitUntilAtLeast(6, -1000) ...
13: waited for 0 ms, retVal=true
14: ---------------
15: about to waitUntilAtLeast(15, -1000) ...
16: waited for 0 ms, retVal=false
17: ---------------
18: about to waitUntilAtLeast(999, 5000) ...
19: just set value to 11
20: just set value to 12
21: just set value to 13
22: just set value to 14
23: waited for 5000 ms, retVal=false
24: ---------------
25: about to waitUntilAtLeast(20, 0) ...
26: just set value to 15
27: just set value to 16
28: just set value to 17
29: just set value to 18
30: just set value to 19
31: just set value to 20
32: waited for 5050 ms, retVal=true
33: ---------------

Summary

As you build larger applications in Java, you will sometimes need to specify limits as to how long you want one thread to wait for a certain condition to be met. Because the wait() method returns when notified and returns when it times out, steps need to be taken to keep track of how much more time (if any) should be spent waiting for the condition to be met. In this chapter, I showed you a technique for doing this by calculating an ending time before waiting, and then waiting again if this time has not yet been reached.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.221.36.208