8.6 A Solution with a Hidden Race Condition

 < Day Day Up > 



8.6 A Solution with a Hidden Race Condition

As illustrated in Section 8.5, the correct approach to using a synchronized block in Java is often to lock less than an entire method. When trying to decide how much of a method to synchronize, a programmer should realize that a particular dynamic occurs with using synchronized locks. If too much of the object is locked, weird behavior (as described in Section 8.5), performance problems, or even deadlock can occur. If too little is locked, race conditions can occur. It is often crucial that the locking be done properly or the program will fail. Normally, a system should be designed to solve the user's problem, but synchronization is so critical that there may be times when the locking scheme to be used might affect the design of the system. [3] In the case of Exhibit 2 (Program8.2), the problem was that too much of the run method was in the synchronized block; however, some portion of the run method must be synchronized to allow the wait method to be called. It is the job of the programmer to figure out how much actually needs to be locked and to lock only that portion of the program.

For the animated thread in Exhibit 2 (Program8.2), a programmer could naively say that, because the only statement in the run method that requires the lock is the wait statement, only the wait statement should be in the synchronized block. This solution is shown in Exhibit 4 (Program8.4). Because the sleep method, which was the problem in Exhibit 2 (Program8.2), is outside of the synchronized lock, the method will not have a problem with holding up the GUI thread. The only question left to answer is whether or not Exhibit 4 (Program8.4) is safe. When this solution is run, it does appear to behave correctly, and this program could be run for years without a problem. But, as has been continually stressed, you cannot test to show that a concurrent program is correct. Exhibit 4 (Program8.4) does, in fact, have a race condition. The race condition occurs if the path object is created before the wait method can be called in the run method. The GUI thread runs in the draw method, reaches the end of the path, and sends a notify to the thread. This notify would be lost, and the ball thread would be stuck at a wait for which a notify never occurs.

Exhibit 4: Program8.4: Concurrent Animation with a Race Condition

start example

 import java.awt.*; import java.util.*; import animator.*; /**  * This class animates a ball in a thread using the animator  * component. It normally works but has a potential race  * condition.  */ 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, which 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 creation of the path  * before the synchronized lock means that the path could  * finish and the notify be called before the wait is  * called.  */   public 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;         }         synchronized (this) {           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 a Path object is required to have at least one step, the odds greatly favor that this program will run correctly close to 100% of the time. A programmer might argue, then, that because the problem is unlikely, the race condition can be ignored. Just because a problem seldom arises does not mean that it will never happen. What is likely is that this program will be maintained in the future by another programmer who does not understand the timing relationship between the creation of the path variable and the wait method call in the run method. This programmer might insert a time-intensive task between the creation of the path and the wait call, effectively destroying the timing interaction between the ball thread calling wait and the GUI thread calling notify. Doing so would lead to a much greater probability that the notify will occur before the wait, thus causing a bug that is difficult to reproduce and even more difficult to find and correct.

[3]An example is the modifications to the Control Panel for the animator (see Chapter 9), which require the programmer to make sure that a wait call never occurs while the program is running in the GUI thread.



 < 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