Home
Synchronization and concurrency
ConcurrentHashMap
Atomic variables
|
|
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.
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.
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.
We round off our discussion on the next page with a look at some common problems with wait() and notify() in Java.
Notes:
1. One reason that this might happen would be if an OS only implemented 'notify all'
functionality, and not single-thread notify. In this case, a JVM might be forced to
implement notify() as a 'notify all' action. According to the spec, this
would be a legal JVM implementation.
(In reality, I believe all
mainstream OS's provide both calls.)
Written by Neil Coffey. Copyright © Javamex UK 2008. All rights reserved.