Using the Class-Level Lock in a synchronized Statement

Chapter 8 - Inter-thread Communication

Java Thread Programming
Paul Hyde
  Copyright 1999 Sams Publishing

The Wait/Notify Mechanism
The wait/notify mechanism allows one thread to wait for a notification from another thread that it may proceed. Typically, the first thread checks a variable and sees that the value is not yet what it needs. The first thread invokes wait() and goes to sleep (using virtually zero processor resources) until it is notified that something has changed. Eventually, the second thread comes along and changes the value of the variable and invokes notify() (or notifyAll() ) to signal the sleeping thread that the variable has been changed.
The wait/notify mechanism does not require that a variable be checked by one thread and set by another. However, it is generally a good idea to use this mechanism in conjunction with at least one variable. Doing so helps in avoiding missed notifications and in detecting early notifications (Ill tell you about both later in this chapter).
Minimal Wait/Notify
At a bare minimum, you need an object to lock on and two threads to implement the wait/notify mechanism. For thread notification, two methods are available: notify() and notifyAll() . For simplicity, Ill use notify() here and explain the difference later in this chapter.
Imagine that there is a member variable, valueLock , that will be used for synchronization:
private Object valueLock = new Object();
The first thread comes along and executes this code fragment:
synchronized (valueLock) {
    try {
        valueLock. wait() ;
    } catch ( InterruptedException x) {
        System.out.println( interrupted while waiting);
    }
}
The wait() method requires the calling thread to have previously acquired the object-level lock on the target object. In this case, the object that will be waited upon is valueLock , and two lines before the valueLock.wait() statement is the synchronized(valueLock) statement. The thread that invokes the wait() method releases the object-level lock and goes to sleep until notified or interrupted. If the waiting thread is interrupted, it competes with the other threads to reacquire the object-level lock and throws an InterruptedException from within wait() . If the waiting thread is notified, it competes with the other threads to reacquire the object-level lock and then returns from wait() .
Many times, a thread is interrupted to signal it that it should clean up and die (see Chapter 5 ). The statements used to wait can be slightly rearranged to allow the InterruptedException to propagate up further:
try {
    synchronized (valueLock) {
        valueLock. wait() ;
    }
} catch (InterruptedException x) {
    System.out.println(interrupted while waiting);
    // clean up, and allow thread to return from run()
}
Instead of catching InterruptedException , methods can simply declare that they throw it to pass the exception further up the call chain:
public void someMethod() throws InterruptedException {
    // ...
    synchronized (valueLock) {
        valueLock. wait() ;
    }
    // ...
}
The thread doing the notification comes along and executes this code fragment:
synchronized (valueLock) {
    valueLock. notify() ;  // notifyAll() might be safer...
}
This thread blocks until it can get exclusive access to the object-level lock for valueLock . After the lock is acquired, this thread notifies one of the waiting threads. If no threads are waiting, the notification effectively does nothing. If more than one thread is waiting on valueLock , the thread scheduler arbitrarily chooses one to receive the notification. The other waiting threads are not notified and continue to wait. To notify all waiting threads (instead of just one of them), use notifyAll() (discussed later in this chapter).
Typical Wait/Notify
In most cases, a member variable is checked by the thread doing the waiting and modified by the thread doing the notification. The checking and modification occur inside the synchronized blocks to be sure that no race conditions develop.
This time, two member variables are used:
private boolean value = false;
private Object valueLock = new Object();
The value variable is checked by the thread doing the waiting and is set by the thread doing the notification. Synchronization on valueLock controls concurrent access to value .
The first thread comes along and executes this code fragment:
try {
    synchronized (valueLock) {
        while ( value != true) {
            valueLock.wait();
        }
        // value is now true
    }
} catch ( InterruptedException x ) {
    System.out.println(interrupted while waiting);
}
After acquiring the object-level lock for valueLock , the first thread checks value to see if it is true . If it is not, the thread executes wait() , releasing the object-level lock. When this thread is notified, it wakes up, reacquires the lock, and returns from wait() . To be sure that it was not falsely notified (see the Early Notification discussion later in this chapter), it re- evaluates the while expression. If value is still not true , the thread waits again. If it is true , the thread continues to execute the rest of the code inside the synchronized block.
While the first thread is waiting, a second thread comes along and executes this code fragment:
synchronized (valueLock) {
    value = true;
    valueLock.notify ();  // notifyAll() might be safer...
}
When the first thread executes the wait() method on valueLock , it releases the object-level lock it was holding. This release allows the second thread to get exclusive access to the object-level lock on valueLock and enter the synchronized block. Inside, the second thread sets value to true and invokes notify() on valueLock to signal one waiting thread that value has been changed.
Wait/Notify with synchronized Methods
In the previous examples, valueLock was used with the synchronized statement to require that threads get exclusive access to the object-level lock associated with valueLock . Sometimes, the class is designed to synchronize on this instead of another object. In this case, the synchronized method modifier can be used instead of a synchronized statement. The following code fragments are an adaptation of the previous example.
As before, a member variable value is initially set to false :
private boolean value = false;
The first thread ( threadA ) comes along and invokes this waitUntilTrue() method:
public synchronized void waitUntilTrue()
                throws InterruptedException {
    while ( value == false) {
        wait();
    }
}
While threadA is blocked on the wait() , a second thread ( threadB ) comes along and executes this method, passing in true for newValue :
public synchronized void setValue(boolean newValue) {
    if (newValue != value) {
        value = newValue;
        notify();   // notifyAll() might be safer...
    }
}
Note that both methods are synchronized and are members of the same class. In addition, both threads are invoking methods on the same instance of this class. The waitUntilTrue() method (with the wait() inside) declares that it might throw an InterruptedException . In this case, when threadB passes in true , value is changed and notify() is used to signal the waiting threadA that it may proceed. threadA wakes up, reacquires the object-level lock on this , returns from wait() , and re-evaluates the while expression. This time, value is true , and threadA will return from waitUntilTrue() .
Figure 8.1 shows a relative timeline of when everything occurs. When threadA wants to invoke waitUntilTrue() , it must first get exclusive access to the object-level lock on this (time T1). Just after threadA acquires the lock, it enters waitUntilTrue() (time T2). threadA determines that it must wait for value to change, so it invokes the wait() method on this (time T3). Just after threadA enters wait() , it releases the object-level lock on this (time T4).
Some time later, threadB acquires the lock (time T5) and enters the setValue() method (time T6). While inside setValue() , threadB sets value to true and invokes notify() . This action causes threadA to be notified, but threadA cannot return from wait() until it can get the lock, and threadB is still holding the lock. threadB then returns from setValue() (time T7) and releases the lock (time T8).
Soon after threadB releases the object-level lock on this , threadA is able to reacquire it (time T9) and returns from wait() (time T10). threadA sees that value is now true and proceeds to execute the rest of the waitUntilTrue() method. threadA returns from waitUntilTrue() (time T11) and releases the lock (time T12).
There are some particular points of interest in Figure 8.1. First, notice the small intervals of time between some events. For example, there is a very short, but non-zero interval between the time that threadA gets the lock (time T1) and the time that it is inside waitUntilTrue() (time T2). Also notice that threadA is inside wait() from time T3 through time T10, but releases the lock shortly after entering (time T4) and reacquires it just before returning (time T9). A thread might spend quite a bit of time inside a wait() method, but it is not holding the lock for most of that time. On the other hand, the time spent inside the notify() method is very brief, and the lock is held the whole time.
Figure 8.1: Timeline of events for wait/notify example.

Toc


Java Thread Programming
Java Thread Programming
ISBN: 0672315858
EAN: 2147483647
Year: 2005
Pages: 149
Authors: Paul Hyde

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