WaitNotify


Wait/Notify

Java provides a mechanism to help you coordinate actions between two or more threads. Often you need to have one thread wait until another thread has completed its work. Java provides a mechanism known as wait/notify. You can call a wait method from within a thread in order to idle it. Another thread can wake up this waiting thread by calling a notify method.

An example? Let's rock! A clocktic-toc!

In this example, you'll build a clock class suitable for use in a clock application. A user interface (UI) for the clock application could display either a digital readout or an analog representation, complete with hour, minute, and second hands. While the UI is busy creating output that the user sees, the actual Clock class can execute a separate thread to constantly track the time. The Clock thread will loop indefinitely. Every second, it will wake up and notify the user interface that the time has changed.


The UI class implements the ClockListener interface. You pass a ClockListener reference to a Clock object; the clock can call back to this listener with the changed time. With this listener-based design, you could replace an analog clock interface with a digital clock interface and not have to change the clock class (see Figure 13.4).

Figure 13.4. A Clock Listener


The tough part is going to be writing a test. Here's a strategy:

  • Create a mock ClockListener that stores any messages it receives.

  • Create the Clock instance, passing in the mock object.

  • Start the clock up and wait until five messages have been received by the mock.

  • Ensure that the messages received by the ClockListener, which are date instances, are each separated by one second.

The test below performs each of these steps. The tricky bit is getting the test to wait until the mock gets all the messages it wants. I'll explain that part, but I'll let you figure out the rest of the test, including the verification of the tics captured by the test listener.

 package sis.clock; import java.util.*; import junit.framework.*; public class ClockTest extends TestCase {    private Clock clock;    private Object monitor = new Object();               // 1    public void testClock() throws Exception {       final int seconds = 5;       final List<Date> tics = new ArrayList<Date>();       ClockListener listener = new ClockListener() {          private int count = 0;          public void update(Date date) {             tics.add(date);             if (++count == seconds)                synchronized(monitor) {                 // 2                   monitor.notifyAll();                  // 3                }          }       };       clock = new Clock(listener);       synchronized(monitor) {                          // 4          monitor.wait();                                // 5       }       clock.stop();       verify(tics, seconds);    }    private void verify(List<Date> tics, int seconds) {       assertEquals(seconds, tics.size());       for (int i = 1; i < seconds; i++)          assertEquals(1, getSecondsFromLast(tics, i));    }    private long getSecondsFromLast(List<Date> tics, int i) {       Calendar calendar = new GregorianCalendar();       calendar.setTime(tics.get(i));       int now = calendar.get(Calendar.SECOND);       calendar.setTime(tics.get(i - 1));       int then = calendar.get(Calendar.SECOND);       if (now == 0)          now = 60;       return now - then;    } } 

After the test creates the Clock instance, it waits (line 5). You can send the wait message to any objectwait is defined in the class Object. You must first obtain a lock on that same object. You do so using either a synchronized block (line 4) or synchronized method, as mentioned above. In the test, you use an arbitrary object stored in the monitor field (line 1) as the object to lock and wait on.

What does the test wait for? In the mock listener implementation, you can store a count variable that gets bumped up by 1 each time the update(Date) method is called. Once the desired number of messages is received, you can tell the ClockTest to stop waiting.

From an implementation standpoint, the test thread waits until the listener, which is executing in another thread, says to proceed. The listener says that things can proceed by sending the notifyAll message to the same object the test is waiting on (line 3). In order to call notifyAll, you must first obtain a lock against the monitor object, again using a synchronized block.

But ... wait! The call to wait was in a synchronized block, meaning that no other code can obtain a lockincluding the synchronized block wrapping the notifyAll methoduntil the synchronized block exits. Yet it won't exit until the waiting is done, and the waiting isn't done until notifyAll is called. This would seem to be a Catch-22 situation.

The trick: Behind the scenes, the wait method puts the current thread on what is called a "wait set" for the monitor object. It then releases all locks before idling the current thread. Thus when code in the other thread encounters the synchronized block wrapping the notifyAll call, there is no lock on the monitor. It can then obtain a lock (line 2). The notifyAll call requires the lock in order to be able to send its message to the monitor.

You may want to go over the discussion of wait/notify a few times until it sinks in.

Here is the ClockListener interface and the Clock class implementation:

 // sis.clock.ClockListener package sis.clock; import java.util.*; public interface ClockListener {    public void update(Date date); } // sis.clock.Clock package sis.clock; import java.util.*; public class Clock implements Runnable {    private ClockListener listener;    private boolean run = true;    public Clock(ClockListener listener) {       this.listener = listener;       new Thread(this).start();    }    public void stop() {       run = false;    }    public void run() {        while (run) {          try {Thread.sleep(1000); }          catch (InterruptedException e) {}          listener.update(new Date());       }    } } 

The run method in Clock uses the simple technique of testing a boolean flag, run, each time through a while loop to determine when to stop. The stop method, called by the test after its waiting is complete, sets the run boolean to false.

There is a flaw in the Clock implementation. The run method sleeps for at least a full second. As I noted earlier, a sleep call may take a few additional nanoseconds or milliseconds. Plus, creating a new Date object takes time. This means that it's fairly likely that the clock could skip a second: If the last time posted was at 11:55:01.999, for example, and the sleep plus additional execution took 1,001 milliseconds instead of precisely 1,000 milliseconds, then the new time posted would be 11:55:03.000. The clock UI would show 11:55:01, then skip to 11:55:03. In an analog display, you'd see the second hand jump a little bit morenot that big a deal in most applications. If you run enough iterations in the test, it will almost certainly fail.

Here's an improved implementation:

 public void run() {    long lastTime = System.currentTimeMillis();    while (run) {       try {Thread.sleep(10); }       catch (InterruptedException e) {}       long now = System.currentTimeMillis();       if ((now / 1000) - (lastTime / 1000) >= 1) {          listener.update(new Date(now));          lastTime = now;       }    } } 

Could you have written a test that would have uncovered this defect consistently? One option for doing so might include creating a utility class to return the current milliseconds. You could then mock the utility class to force a certain time.

The test for Clock is a nuisance. It adds 5 seconds to the execution time of your unit tests. How might you minimize this time? First, it probably doesn't matter if the test proves 5 tics or 2. Second, you might modify the clock class (and test accordingly) to support configurable intervals. Instead of 1 second, write the test to demonstrate support for hundredths of a second (like a stopwatch would require).



Agile Java. Crafting Code with Test-Driven Development
Agile Javaв„ў: Crafting Code with Test-Driven Development
ISBN: 0131482394
EAN: 2147483647
Year: 2003
Pages: 391
Authors: Jeff Langr

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net