You can come out of the wait( ) due to a notify( )
or notifyAll( ), or by letting the clock run out.
One fairly unique aspect of wait( ), notify( ), and notifyAll( ) is that these methods are part of the base class Object and not part of Thread, as is sleep( ). Although this seems a bit strange at first—to have something that’s exclusively for threading as part of the universal base class—it’s essential because they manipulate the lock that’s also part of every object. As a result, you can put a wait( ) inside any synchronized method, regardless of whether that class extends Thread or implements Runnable. In fact, the only place you can call wait( ), notify( ), or notifyAll( ) is within a synchronized method or block (sleep( ) can be called within non-synchronized methods since it doesn’t manipulate the lock). If you call any of these within a method that’s not synchronized, the program will compile, but when you run it, you’ll get an IllegalMonitorStateException with the somewhat nonintuitive message “current thread not owner.” This message means that the thread calling wait( ), notify( ), or notifyAll( ) must “own” (acquire) the lock for the object before it can call any of these methods.
You can ask another object to perform an operation that manipulates its own lock. To do this, you must first capture that object’s lock. For example, if you want to notify( ) an object x, you must do so inside a synchronized block that acquires the lock for x:
synchronized(x) {
x.notify();
}
Typically, wait( ) is used when you’re waiting for some condition that is under the control of forces outside of the current method to change (typically, this condition will be changed by another thread). You don’t want to idly wait while testing the condition inside your thread; this is called a “busy wait” and it’s a very bad use of CPU cycles. So wait( ) allows you to put the thread to sleep while waiting for the world to change, and only when a notify( ) or notifyAll( ) occurs does the thread wake up and check for changes. Thus, wait( ) provides a way to synchronize activities between threads.
As an example, consider a restaurant that has one chef and one waitperson. The waitperson must wait for the chef to prepare a meal. When the chef has a meal ready, the chef notifies the waitperson, who then gets the meal and goes back to waiting. This is an excellent example of thread cooperation: The chef represents the producer, and the waitperson represents the consumer. Here is the story modeled in code:
//: c13:Restaurant.java
// The producer-consumer approach to thread cooperation.
import com.bruceeckel.simpletest.*;
class Order {
private static int i = 0;
private int count = i++;
public Order() {
if(count == 10) {
System.out.println("Out of food, closing");
System.exit(0);
}
}
public String toString() { return "Order " + count; }
}
class WaitPerson extends Thread {
private Restaurant restaurant;
public WaitPerson(Restaurant r) {
restaurant = r;
start();
}
public void run() {
while(true) {
while(restaurant.order == null)
synchronized(this) {
try {
wait();
} catch(InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println(
"Waitperson got " + restaurant.order);
restaurant.order = null;
}
}
}
class Chef extends Thread {
private Restaurant restaurant;
private WaitPerson waitPerson;
public Chef(Restaurant r, WaitPerson w) {
restaurant = r;
waitPerson = w;
start();
}
public void run() {
while(true) {
if(restaurant.order == null) {
restaurant.order = new Order();
System.out.print("Order up! ");
synchronized(waitPerson) {
waitPerson.notify();
}
}
try {
sleep(100);
} catch(InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public class Restaurant {
private static Test monitor = new Test();
Order order; // Package access
public static void main(String[] args) {
Restaurant restaurant = new Restaurant();
WaitPerson waitPerson = new WaitPerson(restaurant);
Chef chef = new Chef(restaurant, waitPerson);
monitor.expect(new String[] {
"Order up! Waitperson got Order 0",
"Order up! Waitperson got Order 1",
"Order up! Waitperson got Order 2",
"Order up! Waitperson got Order 3",
"Order up! Waitperson got Order 4",
"Order up! Waitperson got Order 5",
"Order up! Waitperson got Order 6",
"Order up! Waitperson got Order 7",
"Order up! Waitperson got Order 8",
"Order up! Waitperson got Order 9",
"Out of food, closing"
}, Test.WAIT);
}
} ///:~
Order is a simple self-counting class, but notice that it also includes a way to terminate the program; on order 10, System.exit( ) is called.
A WaitPerson must know what Restaurant they are working for because they must fetch the order from the restaurant’s “order window,” restaurant.order. In run( ), the WaitPerson goes into wait( ) mode, stopping that thread until it is woken up with a notify( ) from the Chef. Since this is a very simple program, we know that only one thread will be waiting on the WaitPerson’s lock: the WaitPerson thread itself. For this reason it’s safe to call notify( ). In more complex situations, multiple threads may be waiting on a particular object lock, so you don’t know which thread should be awakened. The solutions is to call notifyAll( ), which wakes up all the threads waiting on that lock. Each thread must then decide whether the notification is relevant.
Notice that the wait( ) is wrapped in a while( ) statement that is testing for the same thing that is being waited for. This seems a bit strange at first—if you’re waiting for an order, once you wake up the order must be available, right? The problem is that in a multithreading application, some other thread might swoop in and grab the order while the WaitPerson is waking up. The only safe approach is to always use the following idiom for a wait( ):
while(conditionIsNotMet)
wait( );
This guarantees that the condition will be met before you get out of the wait loop, and if you have either been notified of something that doesn’t concern the condition (as can happen with notifyAll( )), or the condition changes before you get fully out of the wait loop, you are guaranteed to go back into waiting.
A Chef object must know what restaurant he or she is working for (so the Orders can be placed in restaurant.order) and the WaitPerson who is picking up the meals, so that WaitPerson can be notified when an order is ready. In this simplified example, the Chef is generating the Order objects, then notifying the WaitPerson that an order is ready.
Observe that the call to notify( ) must first capture the lock on waitPerson. The call to wait( ) in WaitPerson.run( ) automatically releases the lock, so this is possible. Because the lock must be owned in order to call notify( ), it’s guaranteed that two threads trying to call notify( ) on one object won’t step on each other’s toes.
The preceding example has only a single spot for one thread to store an object so that another thread can later use that object. However, in a typical producer-consumer implementation, you use a first-in, first-out queue in order to store the objects being produced and consumed. See the exercises at the end of the chapter to learn more about this.