Friday, February 27, 2009

Know wait(), notify() and notifyAll() methods

What the role of wait(), notify() and notifyAll() in JAVA?

These all three effectively allow one thread to signal to another. Without these, various constructs used in concurrent programming would be difficult and inefficient to implement.

See how signalling between threads works using 'wait and notify':

We can call the wait() method of any Java object, which suspends the current thread. The thread is said to be "waiting on" the given object.

Another thread calls the notify() method of the same Java object. This "wakes up" one of the threads waiting on that object.

What's involved with wait/notifty?

The following issues then generally arise:

When do you use wait/notify? we'll look first at a simple thread pool, then later at thread coordination;

How do you use wait/notify?

When to use notifyAll (which 'wakes up' multiple waiting threads as opposed to just one)?

using timed waits for cases where we don't want the waiting thread to be allowed to wait forever.

When to use wait()/notify() in Java?

If you are using a version prior to Java 5, then the wait/notify mechanism can be a key part of thread programming. It is generally used in situations where you need to communicate between two threads. This includes cases such as the following:

For controlling shared resources ("pooling") such as database connections, If all resources are currently in use, one thread can wait to be notified that a resource has become available.

For background execution or coordinating multi-threaded execution: the controlling thread can wait for other threads to notify it of completion of a task.

For creating thread pools or job queues on a server: a fixed number of threads would sit waiting to be notified that a new job had been added to the list.

Wait()/notify() in Java 5

As of Java 5, there is less need for programmers to use wait()/notify(), since other classes are available in the Java concurrency package (java.util.concurrent) to handle these common situations.

For example:

In a common producer-consumer pattern, such as a logging thread, it is generally preferable to use a blocking queue;

To coordinate threads, for example to parallelise a task, it is generally more convenient to use a countdown latch.

Next: how to use wait/notify

On the next page, we'll continue by looking at how to use wait/notify in Java.

How to use wait() and notify()?

I've mentioned that the wait/notify mechanism is essentially a way to communicate between threads. In a nutshell, the idea is as follows:

One or more threads waiting for a signal; another thread comes along and notifies the waiting threads (i.e. "wakes it/them up" with the signal).

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 connections = createConnections();

private List createConnections() {

List conns = new ArrayList(5);

for (int i = 0; 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:

timed waits;

notifyAll();

thread coordination using wait/notify.

The notifyAll() method

An alternative to notify() is notifyAll(). As the name implies, this method wakes up all threads that are waiting on the given object. So which is appropriate where?

The notify() method is generally used for resource pools, where there are an arbitrary number of "consumers" or "workers" that take resources, but when a resource is added to the pool, only one of the waiting consumers or workers can deal with it. The notifyAll() method is actually used in most other cases. Strictly, it is required to notify waiters of a condition that could allow multiple waiters to proceed. But this is often difficult to know. So as a general rule, if you have no particular logic for using notify(), then you should probably use notifyAll(), because it is often difficult to know exactly what threads will be waiting on a particular object and why. For example, imagine we have a fixed-size buffer object. Threads can add objects to the buffer, but must wait if the buffer is full. The class also has a clear() method to empty the buffer:

public class Buffer {

private List buffer = new ArrayList();

public void addItem(V obj) {

synchronized (buffer) {

while (buffer.size() >= maxSize) {

buffer.wait();

}

buffer.add(obj);

}

}

public void clear() {

synchronized (buffer) {

buffer.clear();

buffer.notifyAll();

}

}

}

Now, it's crucial that the clear() method calls notifyAll() because it can't tell how many threads are waiting on the buffer, and it performs an action that can potentially allow various of those threads to proceed.

Generally, if you accidentally call notifyAll() where only notify() is necessary, this should not cause your program logic to fail: at worst it will cause some unnecessary context switches as a bunch of threads are in turn woken up, only to find that the condition they were waiting for has not yet been met, and that they must immediately wait again. But the converse is not true, as in the Buffer.clear() method above: here, notifyAll() is necessary because there could be several threads waiting, and clear() is the only method that does something to allow them to proceed.

Of course, the semantics of the synchronized lock still apply with notifyAll(). That is to say, each of the awoken threads will, in turn, acquire the lock and proceed. The next awoken thread 'in the queue' will proceed only after the previous one has released the lock, either by exiting the synchronized block or by re-entering the wait() method.

Using wait(), notify() and notifyAll() in Java (ctd)

On the previous page, we showed how to create a very simple connection pool, in which a thread could call a getConnection() method. If no connection was currently available, this method would wait for one to become available. A downside of it is that if no connection became avaialable, the method would essentially wait forever. A functionality that we often want in this case is to say "wait for up to n seconds, else give up". And we can get that with a timed wait.

Timed waits

In a timed wait, we pass in a parameter to the wait() method specifying how long we want to wait for. Up to Java 1.4, the maximum wait time is specified in milliseconds. So, the following would wait for up to 3 seconds:

// Wait for up to 3 seconds for a connection

connections.wait(3000);

From Java 5 onwards, an extra parameter allows you to specify the wait time down to the nanosecond. In reality, no mainstream operating system provides that level of granularity (to within a few milliseconds, or multiples of the interrupt period– often 10 milliseconds– is typical). But in principle at least, some real-time operating systems may provide more granularity than the millisecond.

So, when we're woken up, how do we know if it was because we were notified or because we timed out? The answer is, we don't know why we were woken up. Just as actually we don't even with the non-timed wait (in principle, the OS could just wake us up for a laugh1). So in a case such as this, if we're woken up and there is no connection in the list, we need to explicitly time how long we were waiting (e.g. via System.currentTimeMillis()) and decide whether to give up or wait again.

No comments:

Post a Comment