8.8 Notification Objects

 < Day Day Up > 



8.8 Notification Objects

8.8.1 What Are Notification Objects?

At times, when implementing a concurrent program, a programmer may not want to wait on the "this" object and instead wants to wait on another object for a number of reasons. The "this" object might have a lock that cannot be dropped, or an object might be created and used as part of a cooperative synchronization that requires a significant amount of code between where it is created and used, as with the path object in Exhibit 5 (Program8.5). There might be a number of threads that are waiting on a component, and the component might want to notify a specific thread, as is shown in the readers/ writers and first-in/first-out binary semaphore problems in Chapter 9. Generically, the object other than "this" that executes wait and notify is referred to as a notification object (NO). However, an NO is more than just the object itself; it is a mechanism needed to safely implement obtaining the lock on the object and notifying on the object. So, an NO is the object to be notified on and the pattern used to lock it. Even though the "this" object can be used to notify a specific thread, it will not be considered an NO. How to implement an NO is relatively straightforward, even if the logic behind each step is not. The next section describes how to implement an NO, and in Chapter 9 presents some examples of how they are used.

8.8.2 Implementing a Notifying Object

The implementation of an NO takes advantage of two properties of Java:

  • In Java, a local identifier to a variable is on the stack and thus can only be referenced in the current context (i.e., thread); therefore, a thread can always safely access objects that are only referenced by a local identifier, because they have no scope outside of that method. However, a variable that is referenced by an identifier that is part of the class definition for an object has a scope of all methods in that object. Variables do not have a scope (they are just memory on the heap) but take on the scope of the identifier that references them. So, a variable created with a local identifier can later be given a classwide scope by setting it to an identifier defined as part of the class. This is consistent with Chapter 4, where a distinction is made between the identifier and the variable, and the fact that a variable which takes on the scope of the identifier that references it can be used to change the scope of a local variable to one that has scope in all the objects in a method.

  • In Java, the synchronization lock for an object is on the variable, not the identifier; therefore, if a lock is obtained on a variable with a local identifier and that variable is then set to a reference for an identifier for an object, the thread has in effect co-opted the lock on the object identifier. When doing this, it is always safe to obtain the lock on the variable for the local identifier and then, in a synchronized block on the "this" object, to set that variable to an object identifier, giving the variable a larger, more permanent scope. It is generally not safe to set the variable with a local identifier to an object identifier outside of a synchronized block on the "this" object.

To understand the implications of these two properties, consider the following simple class. In this class, NotifyLockExample, the variable notificationObject is locked in both method1 and method2; however, in method1, an object is created with a reference to a variable temp, and a lock is obtained on that variable. The lock is obtained on the notificationObject by later setting the identifier notificationObject to the temp variable. In method2, the lock on the variable notificationObject is obtained on the variable directly referenced by the notificationObject identifier. If thread1 calls method1, thread2 calls method2, and thread2 runs first, it is possible in this program for thread1 and thread2 both to hold the lock for what appears to be the notificationObject. How? When we have two different objects, the notify in thread2 is simply lost because it is notifying on the old notificationObject variable, not the one on which thread1 is waiting:

  public class NotifyLockExample {    private Object notificationObject = new Object();    public void method_1() {      object temp = new Object();      synchronized(temp) {        notificationObject = temp;        // temp and notificationObject are now the same        // object, so it is safe to wait on notificationObject.        notificationObject.wait();      }    }    public void method_2() {      synchronized(notificationObject) {      // NOTE: This next statement can result in an      // exception being thrown because the notify call      // is not on the object for the synchronized block.      // The notificationObject in the synchronized block is      // is the original one, and the one here is the new one.      notificationObject.notify();      }    }  } 

This behavior might be surprising to some, but it is a direct result of using the objects in the program. This behavior did not occur in any earlier programs because the object used was always the "this" object. When the "this" object was used, the reference could not be set to another object, so it was not possible to manipulate the objects on which the lock was held, thus the use of locking on the "this" object was safe. This example should make it clear that locking on an object other than the "this" object is inherently more complex. It should also reinforce why understanding the behavior of the JVM and how it manages memory is important to all programmers and why it can never be safely ignored.

Fortunately it is easy to get around the problems with notification objects. The following pattern always implements a safe notification object and is easy to implement:

  • In the method that calls wait:

    • Create a temporary object of any type.

    • When ready to do the wait, synchronize on the temporary object.

    • Synchronize on the current object.

    • Set an instance object to the temporary object; the lock on the temporary object is still maintained, but it now has object scope.

    • End the synchronized block on the current object, dropping the lock on the current object.

    • Call wait on the temporary (now instance) object.

  • In the method that calls notify:

    • Synchronize on the current object.

    • Synchronize on the instance object from step 1 above.

    • Call notify on the instance object.

This is shown in the following code fragment for two methods used in Exhibit 6 (Program8.6) to implement another correct version of the program that uses the animator with threads:

 public class NOLockExample {   private Object notificationObject = new Object();   public void method_1() {   object temp = new Object();   synchronized(temp) {     synchronized(this) {     notificationObject = temp;     }     notificationObject.wait();   }   }   public synchronized void method_2() {   synchronized(notificationObject) {     notificationObject.notify();   }   } } 

Exhibit 6: Program8.6: Implementation of the Notification Object

start example

 import java.awt.*; import java.util.*; import animator.*; /**  * This class animates a ball in a thread using the animator  * component. It shows how a notification object can be used  * to implement a correct coordination between the GUI and  * ball threads.  */ 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    *  coordination is via a notification object, which is    *  explained in this function's header.    */   public void run() {     Path tempPath;// Local variable to set to myPath; used as          // part of a notification object.     animator.addDrawListener(this);     try {       while(true) {         if (direction = = SOUTH) {           tempPath = new StraightLinePath(410, 205, 10, 205,              50);           direction = NORTH;         }         else if (direction = = NORTH) {           tempPath = new StraightLinePath(10, 205, 410, 205,              50);           direction = SOUTH;         }         else if (direction = = EAST) {           tempPath = new StraightLinePath(205, 410, 205, 10,              50);           direction = WEST;         }         else {// direction = = WEST           tempPath = new StraightLinePath(205, 10, 205, 410,              50);           direction = EAST;         }         synchronized(tempPath) {           synchronized (this) {             myPath = tempPath;           }           myPath.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()) {       synchronized(myPath) {         myPath.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

Exhibit 6 (Program8.6) shows the use of an NO with the ConcurrentBall program. The notification object is combined with state diagrams and the Java Event Model in Chapter 9 to enhance both of these techniques.



 < 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