8.5 Using Wait and Notify to Coordinate Threads

 < Day Day Up > 



8.5 Using Wait and Notify to Coordinate Threads

Chapter 2 showed that the best way to synchronize threads for competitive synchronization is by using wait and notify. These methods are also the best way to handle cooperative synchronization; however, as has already been shown, a notify and a wait call must be inside of a synchronized block, and deciding how to implement that synchronized block cannot be done in a haphazard manner.

Consider Exhibit 2 (Program8.2), which attempts to fix the problem of coordinating the ball thread and the GUI thread by putting a wait call in the run method of the Ball object (and thus in the ball thread) and a notify call in the draw method of the Ball object (and thus in the GUI thread). The two threads then cooperate through the use of the path variable. While the GUI thread is moving the ball on the screen in the draw method, the ball thread waits. The movement of the ball is completed when the end of the path is reached (it has no more steps), and the GUI thread then calls notify on the Ball object, freeing the ball thread to continue. The ball thread can now call the sleep method and create another Path object to move the ball back.

Exhibit 2: Program8.2: Thread Animation with the Run Method Synchronized

start example

 import java.awt.*; import java.util.*; import animator.*; /**  * This class animates a ball in a thread using the animator  * component but fails because the run method is  * completely synchronized.  */ public class ConcurrentBall implements DrawListener, Runnable {   static final int EAST = 0;   static final int WEST = 1;   static final int NORTH = 2;   static final int SOUTH = 3;   int direction;   Path myPath;   Random random;   Animator animator;   /**    * Constructor. Just save information for this ball.    */   public ConcurrentBall(Animator animator, int direction) {     this.animator = animator;     this.direction = direction;     random = new Random(direction);   }   /**    * The run method simulates an asynchronous ball. The    * myPath variable is set here and used in the draw method    * and is intended to coordinate the ball thread running in    * this method and the GUI thread (from the animator)    * running in the draw method. But, the complete    * synchronization of methods forces the GUI thread to    * suspend while it sleeps.    */   public synchronized void run() {     animator.addDrawListener(this);     try {       while(true) {         if (direction = = SOUTH) {           myPath = new StraightLinePath(410, 205, 10, 205, 50);           direction = NORTH;         }         else if (direction = = NORTH) {           myPath = new StraightLinePath(10, 205, 410, 205, 50);           direction = SOUTH;         }         else if (direction = = EAST) {           myPath = new StraightLinePath(205, 410, 205, 10, 50);           direction = WEST;         }         else if (direction = = WEST) {           myPath = new StraightLinePath(205, 10, 205, 410, 50);           direction = EAST;         }       wait();       Thread.sleep(random.nextInt(10000));       }     } catch (InterruptedException e) {     }   }   /**    * Draw is called each time through the animator loop to draw    * the object. It simply uses the path to calculate the    * position of this object and then draws itself at that    * position. When the end of the path is reached, it notifies    * the ball thread.    */   public synchronized void draw(DrawEvent de) {     Point p = myPath.nextPosition();     Graphics g = de.getGraphics();     g.setColor(Color.red);     g.fillOval((int)p.getX(), (int)p.getY(), 15, 15);     if (! myPath.hasMoreSteps()) {       notify();     }   }   /**    * The main method just creates the animator and    * the ball threads and starts them running.    */   public static void main(String args[]) {     Animator animator = new Animator();     ConcurrentBall cb1 = new ConcurrentBall(animator, WEST);     (new Thread(cb1)).start();     ConcurrentBall cb2 = new ConcurrentBall(animator, NORTH);     (new Thread(cb2)).start();     animator.setVisible(true);   } 

end example

Because the wait and notify calls must be in a synchronized block, the simplest way to accomplish this is to synchronize the entire run and draw methods. This is done in Exhibit 2 (Program8.2). Now when the program runs, it completes the movement of the ball from one end of the screen to the other, so the problem with the ball moving while the ball thread is sleeping has been fixed. The ball waits while it is being moved and then sleeps, which is the behavior that is expected. If only one thread is running, the results do, indeed, appear to be correct. However, when two balls are displayed, the results are again disturbing. Now, rather than the balls sleeping different amounts of time, the balls move at the same time on the screen. This is obviously wrong behavior. The application has other problems, as well. For example, if you try to press any buttons or adjust the scrollbar you will find that the GUI thread is locked up, just as in Program7.2. Obviously, this is not what we wanted from the program, but at least the results indicate that whatever is wrong is also affecting the ability of the GUI thread to run.

The problem is a result of the Thread.sleep occurring in the synchronized block for the Ball object. Remember that Thread.sleep does not drop an object lock, so if a sleep occurs in a synchronized block the lock for that object is held while the object sleeps. In this case, the ball thread is holding the lock on the Ball object the entire time it is sleeping. When the GUI thread then tries to get the synchronized lock on the Ball object on entering the draw method, it must wait until the sleep finishes, a new path is created, and the ball calls wait. In fact, because the GUI thread calls the draw method on both balls, it must wait until both ball threads call wait before continuing. So when both balls are waiting, and only when both are waiting, the GUI thread can move the Ball objects. This explains why both balls move together in the program and why the GUI components appear jammed. The GUI thread was forced to wait on a synchronized lock to enter the draw method while the ball threads slept.

This example shows that synchronizing the entire run method is not an appropriate way to get the synchronized lock on the Ball object. What is needed is for the ball thread not to hold the lock on the Ball object while the thread is sleeping. We have only two ways not to hold a synchronized lock; the first is via a call to wait, and the second is to run outside of the synchronized block, thus never obtaining the lock. The second approach is superior in most situations and is explored further in the rest of the chapter.

Another way to make Exhibit 2 (Program8.2) work without changing the scope of the synchronized block is to use a format for the wait call that takes a parameter that is the number of milliseconds to wait for a notify. It has the form "wait(int waitTime)." This wait call will wait waitTime milliseconds for a notify to occur. If a notify call does not occur within that period of time, then the wait call returns as if a notify had occurred. Like any wait call, it still drops the lock when it is executed and must re-obtain the lock before it can begin to run again. Exhibit 3 (Program8.3) uses this format of the wait method instead of sleep. Using this wait call to "sleep" drops the lock on the Ball object, thus allowing the GUI thread to run while the ball thread is "sleeping." This program does work correctly, but it is an inappropriate use of the wait call. This program uses the wait call to force the ball thread to drop the synchronized lock, but if the lock is not needed then why has the thread obtained it? Also, if it is actually needed because this is a critical section, the synchronization has been broken, which could result in a race condition.

Exhibit 3: Program8.3: Breaking Synchronization with a Call to Wait(waitTime)

start example

 import java.awt.*; import java.util.*; import animator.*; /**  * This class animates a ball in a thread using the animator  * component by using a wait(int) call instead of sleep so that  * the object lock is dropped while the component is sleeping.  */ public class ConcurrentBall implements DrawListener, Runnable {   static final int EAST = 0;   static final int WEST = 1;   static final int NORTH = 2;   static final int SOUTH = 3;   int direction;   boolean pleaseNotify = false;   Path myPath;   Random random;   Animator animator;   /**    * Constructor. Just save information for this ball.    */   public ConcurrentBall(Animator animator, int direction) {     this.direction = direction;     this.animator = animator;     random = new Random(direction);   }   /**    * The run method simulates an asynchronous ball. The    * myPath variable is set here and used in the draw method    * and is intended to coordinate the ball thread running in    * this method and the GUI thread (from the animator)    * running in the draw method. This works but uses wait(int),    * which drops the object lock.    */   public synchronized void run() {     animator.addDrawListener(this);     try {       while(true) {         if (direction = = SOUTH) {           myPath = new StraightLinePath(410, 205, 10, 205, 50);           direction = NORTH;         }         else if (direction = = NORTH) {           myPath = new StraightLinePath(10, 205, 410, 205, 50);           direction = SOUTH;         }         else if (direction = = EAST) {           myPath = new StraightLinePath(205, 410, 205, 10, 50);           direction = WEST;         }         else if (direction = = WEST) {           myPath = new StraightLinePath(205, 10, 205, 410, 50);           direction = EAST;         }       pleaseNotify = true;       wait();       pleaseNotify = false;       wait(random.nextInt(10000));       }     } catch (InterruptedException e) {     }   }   /**    * Draw is called each time through the animator loop to draw    * the object. It simply uses the path to calculate the    * position of this object and then draws itself at that    * position. When the end of the path is reached, it notifies    * the ball thread.    */   public synchronized void draw(DrawEvent de) {     Point p = myPath.nextPosition();     Graphics g = de.getGraphics();     g.setColor(Color.red);     g.fillOval((int)p.getX(), (int)p.getY(), 15, 15);     // Do not notify if the wait is for the wait that emulates     // the sleep.     if ((! myPath.hasMoreSteps()) && pleaseNotify) {       notify();     }   }   /**    * The main method just creates the animator and    * the ball threads and starts them running.    */   public static void main(String args[]) {     Animator animator = new Animator();     ConcurrentBall cb1 = new ConcurrentBall(animator, WEST);     (new Thread(cb1)).start();     ConcurrentBall cb2 = new ConcurrentBall(animator, NORTH);     (new Thread(cb2)).start();     animator.setVisible(true);   } } 

end example

Far better than using wait to drop the synchronized lock is to structure the program so that the synchronized lock protects the critical sections and the synchronized lock is not held in non-critical sections. This makes the locking used by the program explicit, as well as making it easier for a programmer to understand and see that it is correct.



 < Day Day Up > 



Creating Components. Object Oriented, Concurrent, and Distributed Computing in Java
The .NET Developers Guide to Directory Services Programming
ISBN: 849314992
EAN: 2147483647
Year: 2003
Pages: 162

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