How do wait and notify really work?

Alex Chaffee

 

 

 

Summary:

  1. consumer grabs lock ( synchronized (lock) )
  2. consumer calls lock.wait(), releasing monitor lock (!!)
  3. producer produces resource
  4. producer grabs lock ( synchronized (lock) )
  5. producer calls lock.notify()
  6. consumer moves from idle to runnable state, waiting to grab lock
  7. producer releases lock
  8. consumer reaquires lock
  9. consumer returns from lock.wait()
  10. consumer consumes resource (still holding lock)
  11. consumer releases lock

 

"Condition synchronization" is the technical name for delaying the execution of a thread until a condition is satisfied. Normally, this condition signifies the completion of another task, or that the object state is now valid again and it is safe to proceed. Normally the thread doing the waiting is called the consumer, and the thread being waited for is the producer.

Conditional delays would be easy to specify, but inefficient to implement as:

await(condition) statement;

Java chooses a simpler mechanism where the programmer must loop according to the condition around a wait:

while ( !condition ) do wait();

Before calling wait() the consumer thread must acquire a lock on some object; it then calls the wait() method of that lock object itself. This is hidden from view if the lock object is the current object; in that case, we recommend that the programmer explicitly use this.wait() to clarify that the lock is taken out on the current object.

We use a while-loop instead of an if-statement because there is no way to restrict a notifyAll() to a particular condition. This thread may wake up even though a different condition has changed. Also, after waking, a thread still may find the condition unsatisfied because another thread had awakened ahead of it.

To awaken a waiting thread, another thread must acquire the same lock object (using synchronized (lock)), and call notify() or notifyAll() on that lock. The other thread then becomes runnable, and the next time it is awakened, it gets a chance to grab the lock and return from the wait state. (However, this does not happen immediately; the producer still holds the lock until the synchronized block ends.)

Use of wait() and notify() is often confusing because programmers often do not have a separate lock object. Instead, they either use the producer thread object, or the resource object itself, as the lock object. This can be cleared up if you remember that there are three players in this game: the producer thread, the consumer thread, and the lock object.

For example, consider the simple problem of reading information from a blocking queue where you want read operations to block waiting for information. Assume the existence of a superclass Queue containing the add(), remove(), and isEmpty() methods.

class BlockingQueue extends Queue {
   public synchronized Object remove() {
      // wait until there is something to read
      while (this.isEmpty()) 
      	this.wait();
      // we have the lock and state we're seeking
      return super.remove();
    }
    public synchronized void add(Object o) {
      super.add(o);
      // tell waiting threads to wake up
      this.notifyAll();
    }
}

Notice that only one read can occur simultaneously because remove() is synchronized.

Because the read operation is destructive (removes an element), it is proper that only one simultaneous read occurs. Oftentimes, reads do not have side effects and there is no theoretical reason to restrict access to a single simultaneous read. The "readers and writers" problem is well known and is solved by having an object control access to the database so that the read/write methods do not have to be synchronized.

 

0 Comments  (click to add your comment)
Comment and Contribute

 

 

 

 

 


(Maximum characters: 1200). You have 1200 characters left.

 

 

About | Sitemap | Contact