How to use wait() and notify()
We've mentioned that the
Java wait/notify
mechanism is essentially a way to communicate
between threads. In a nutshell, the idea is as follows:
- one or more threads sits waiting for a signal;
- another thread comes along and notifies the waiting threads (i.e. "wakes it/them up"
with the signal).
When to use wait/notify?
The wait-notify pattern is used in a broad set of cases where
one thread needs to tell other threads that some event has occurred. It is
commonly used to implement a thread pool or producer-consumer
scenario, where a particular
thread or threads need to "pick up jobs" created by other threads (in this
case, the "event" that has occurred is that a job has arrived for one of
the threads to pick up).
Note that wait/notify is a relatively "low level" mechanism. As of
Java 5, there are other classes providing more intuitive mechanisms. For example,
the BlockingQueue
classes provide a more convenient means of implementing job queues.
For more details, see the separate page on
when to use wait/notify.
Example: implementing a thread pool
Let's look at a common example of when we'd want to do this.
Imagine that we want to implement a connection pool: a list of
Connection objects (encapsulating a connection to a database) of
which we want to create a fixed number and share amongst various threads.
As mentioned above, in Java 5 onwards, this wouldn't commonly be implemented
by the application programmer using the wait/notify mechanism, since better-performing and higher
level classes are available. But pre-Java 5, it was
a common use for wait/notify.
The thread pooling problem is that we want
to implement a call that does the following:
- allows any thread take a connection from the pool, if one is
available;
- else wait for one to become available.
Similarly, we want a call
that allows the thread to return its connection to the pool and:
- when a connection
is returned to the pool, we want to 'hand' that connection to any waiting thread.
First, we create a ConnectionPool class and assume that it has a List
of Connection objects. For the sake of argument, we'll just assume that
all available connections are created when the ConnectionPool is
constructed. (In reality, we'd probably want to create them on the fly, up to
some maximum number.) We'll omit the part of createConnections method
that actually creates the connections: the only interesting feature for our
purposes is that we create a fixed number of connections and add them to an
unsynchronized list: for reasons we'll see in a minute, we'll always
synchronize explicitly on the list when accessing it.
public class ConnectionPool {
private List<Connection> connections = createConnections();
private List<Connection> createConnections() {
List<Connection> conns = new ArrayList<Connection>(5);
for (int i = 0; i < 5; i++) {
... add a Connection to conns
}
return conns;
}
}
Now we implement our getConnection() method. If no connection is
currently available (i.e. connections is empty), then we need to wait
until one becomes available. Then we return the first available connection.
public Connection getConnection() throws InterruptedException {
synchronized (connections) {
while (connections.isEmpty()) {
connections.wait();
}
return connections.remove(0);
}
}
Note first of all that we synchronize on the connection list. We then check
if the list is empty. If, and while, it is, we "wait" on the list. In order to wait
on an object, we must be synchronized on that object. But our thread
will automatically release the lock temporarily while waiting.
Calling wait() means that our thread will be suspended until it is
"notified". Our thread will be "notified", and thus woken up, when another thread
calls notify() on the object that we're waiting on
(in this case,
the connection list). When our thread wakes up, it automatically regains the
lock. We can now check again that the list is not empty, and if it isn't, safely
take out the first connection. This checking and removing will be atomic because
we have the lock on the list. (If you're unsure what this means, see the
section on the synchronized
keyword in Java.)
Now, let's look at the other side of things: the method that a thread calls
to return a connection to the pool:
public void returnConnection(Connection conn) {
synchronized (connections) {
connections.add(conn);
connections.notify();
}
}
Again with a synchronized
lock on the list, we add the given connection to the list.
Then, while still synchronized on it, we call notify() on the connection.
Calling notify() means: "if there is at least one thread waiting on this
object, please wake up one of those threads". In cases such as this, waking
up a single random thread is the functionality we want: we've only added one
connection to the list, so there's no point waking up more than one waiting thread.
Note that we have no control over which waiting thread is woken up. In particular,
we can't say "wake up the one that's been waiting longest". (Nor will most JVMs or
OSs use such a policy: ensuring this kind of "fairness" turns out to decrease throughput
considerably.)
Note that although notify() wakes up one of the waiting threads, the first thing
that that thread needs to do is re-acquire the lock that our thread is currently holding.
So after calling notify(), we should exit the synchronized
block as quickly as possible. If we do something like this:
public void returnConnection(Connection conn) {
synchronized (connections) {
connections.add(conn);
connections.notify();
// bad: woken thread can't start until we
// come out of synchronized block!
updateStatistics(conn);
}
}
then the woken thread won't be able to proceed until our call to updateStatistics()
returns.
A couple of small points about this wait-notify pattern are worth clarifying:
- The waiting thread can be interrupted (either for some spurious OS/hardware
reason or, more likely, because interrupt() is called on the thread from
within Java). Thus, wait() can throw InterruptedException.
Unless you're in code that's dealing with the "outer logic" of a thread's function,
the most appropriate thing is usually just to throw the exception up.
- When a thread is 'awoken' from wait(), it can't tell why it's
being woken. In particular, it isn't necessarily because it has been notified!
(The OS could just spuriously wake it up for some reason.) So we have to check
the size of the list again before pulling out a connection.
- It's crucial that we don't wait if the list isn't empty. If we
did, we would essentially sit waiting for a notify that wasn't about to come. (This is
actually quite a common programming error in this type of code, because when you're
writing it, your mind is saying "I need to wait until there's something in the list".)
Next...
The above example shows the basics of wait/notify. Some more advanced topics include:
If you enjoy this Java programming article, please share with friends and colleagues. Follow the author on Twitter for the latest news and rants.
Editorial page content written by Neil Coffey. Copyright © Javamex UK 2021. All rights reserved.