Trail: Essential Java Classes
|
Lesson: Doing Two or More Tasks At Once: Threads
|
|
Using the notifyAll and wait Methods
The CubbyHole stores its value in a private member
variable called contents.
CubbyHole has another private
member variable, available, that is a boolean.
available is true when
the value has just been put but not yet gotten and is false when the
value has been gotten but not yet put. So, here's one possible
implementation for the put and get methods:
public synchronized int get() { // won't work!
if (available == true) {
available = false;
return contents;
}
}
public synchronized int put(int value) { // won't work!
if (available == false) {
available = true;
value = ;
}
}
As implemented, these two methods won't work. Look at the
get method. What happens if the Producer
hasn't put anything in the CubbyHole and
available isn't true? get does
nothing. Similarly, if the Producer calls put
before the Consumer got the value, put
doesn't do anything.
You really want the Consumer to wait until the
Producer puts something in the CubbyHole and
the Producer must notify the Consumer when
it's done so. Similarly, the Producer must wait until the
Consumer takes a value (and notifies the
Producer of its activities) before replacing it with a new
value. The two threads must coordinate more fully
and can use Object's
wait and notifyAll methods to do so.
Here are the new implementations of get and
put that wait on and notify each other of their
activities:
public synchronized int get() {
while (available == false) {
try {
// wait for Producer to put value
wait();
} catch (InterruptedException e) {
}
}
available = false;
// notify Producer that value has been retrieved
notifyAll();
return contents;
}
public synchronized void put(int value) {
while (available == true) {
try {
// wait for Consumer to get value
wait();
} catch (InterruptedException e) {
}
}
contents = value;
available = true;
// notify Consumer that value has been set
notifyAll();
}
The code in the get method loops until the
Producer has produced a new value. Each time through the
loop, get calls the wait method. The
wait method relinquishes the lock held by the
Consumer on the CubbyHole (thereby allowing
the Producer to get the lock and update the
CubbyHole) and then waits for notification from the
Producer. When the Producer puts something in
the CubbyHole, it notifies the Consumer by
calling notifyAll. The Consumer then comes
out of the wait state, available is now true,
the loop exits, and the get method returns the value in
the CubbyHole.
The put method works in a similar fashion, waiting for the
Consumer thread to consume the current value before
allowing the Producer to produce a new one.
The notifyAll method wakes up all threads waiting on the
object in question (in this case, the CubbyHole). The
awakened threads compete for the lock. One thread gets it, and the
others go back to waiting. The Object class also defines the notify
method, which arbitrarily wakes up one of the threads waiting on this
object.
The Object class contains not only the version of
wait that is used in the producer/consumer example and
which waits indefinitely for notification, but also two other versions
of the wait method:
-
wait(long timeout)
-
Waits for notification or until the
timeout period has elapsed.
timeout is measured in milliseconds.
-
wait(long timeout, int nanos)
-
Waits for notification or until
timeout milliseconds plus
nanos nanoseconds have elapsed.
Note:
Besides using these timed wait methods to synchronize
threads, you also can use them in place of sleep. Both
wait and sleep delay
for the requested amount of time, but
you can easily wake up wait with a notify but a sleeping
thread cannot be awoken prematurely. This doesn't matter too much for
threads that don't sleep for long, but it could be important for
threads that sleep for minutes at a time.
|