What happens when there is more than one thread waiting for the notification? Which thread actually gets the notification when notify() is called? The answer is that it depends: the Java specification doesn’t define which thread gets notified. Which thread actually receives the notification varies based on several factors, including the implementation of the Java virtual machine and scheduling and timing issues during the execution of the program. There is no way to determine, even on a single platform, which of multiple threads receives the notification.
There is another method of the Object class that assists us when multiple threads are waiting for a condition:
The Object class also provides the notifyAll()
method, which helps us in those cases where the program cannot be
designed to allow any arbitrary thread to receive the notification.
This method is similar to the notify()
method,
except that all of the threads that are waiting on the object will be
notified instead of a single arbitrary thread. Just like the
notify()
method, the
notifyAll()
method does not let us decide which
threads get notification: they all get notified. By having all the
threads receive notification, it is now possible for us to work out a
mechanism for the threads to choose among themselves which thread
should continue and which thread(s) should call the
wait()
method again.
Why would you want to wake up all of the threads? There are a few possible reasons, one of which is if there is more than one condition to wait for. Since we cannot control which thread gets the notification, it is entirely possible that a notification wakes up a thread that is waiting for an entirely different condition. By waking up all the waiting threads, we can design the program so that the threads decide among themselves which should execute next.
Another reason is the case where the notification can satisfy multiple waiting threads. Let’s examine a case where we need such control:
public class ResourceThrottle { private int resourcecount = 0; private int resourcemax = 1; public ResourceThrottle (int max) { resourcecount = 0; resourcemax = max; } public synchronized void getResource (int numberof) { while (true) { if ((resourcecount + numberof) <= resourcemax) { resourcecount += numberof; break; } try { wait(); } catch (Exception e) {} } } public synchronized void freeResource (int numberof) { resourcecount -= numberof; notifyAll(); } }
We are defining a new class called the ResourceThrottle class. This
class provides two methods,
getResource()
and
freeResource()
. Both of these methods take a
single parameter that specifies how many resources to grab or
release. The maximum number of resources available is defined by the
constructor of the ResourceThrottle class. This class is similar to
our BusyFlag class, in that our getResource()
method would have to wait if the number of requested resources is not
available. The freeResource()
method also has to
call the notify()
method so that the waiting
threads can get notification when more resources are available.
The difference in this case is that we are calling the
notifyAll()
method instead of the
notify()
method. There are two reasons for this:
It is entirely possible for the system to wake up a thread that needs more resources than are available, even with the resources that have just been freed. If we had used the
notify()
method, another thread that could be satisfied with the current amount of resources would not get the chance to grab those resources because the system picked the wrong thread to wake up.It is possible to satisfy more than one thread with the number of resources we have just freed. As an example, if we free ten resources, we can then let four other threads grab three, four, one, and two resources, respectively. There is not a one-to-one ratio between the number of threads freeing resources and the number of threads grabbing resources.
By notifying all the threads, we solve these two problems with little work. However, all we have accomplished is to simulate a targeted notification scheme. We are not really controlling which threads wake up; instead, we are controlling which thread takes control after they all get notification. This can be very inefficient if there are many threads waiting to get notification, because many wake up only to see that the condition is still unsatisfied, and they must wait again.
If we really need to control which thread gets the notification, we
could also implement an array of objects whose sole purpose is to act
as a waiting point for threads and who are targets of notification of
conditions. This means that each thread waits on a different object
in the array. By having the thread that calls the
notify()
method decide which thread should
receive notification, we remove the overhead of many threads waking
up only to go back to a wait state moments later. The disadvantage of
using an array of objects is, of course, that we will lock on
different objects. This acquisition of many locks could lead to
confusion or, even worse, deadlock. It is also more complicated to
accomplish; we may even have to write a new class just to help with
notification targeting:
public class TargetNotify { private Object Targets[] = null; public TargetNotify (int numberOfTargets) { Targets = new Object[numberOfTargets]; for (int i = 0; i < numberOfTargets; i++) { Targets[i] = new Object(); } } public void wait (int targetNumber) { synchronized (Targets[targetNumber]) { try { Targets[targetNumber].wait(); } catch (Exception e) {} } } public void notify (int targetNumber) { synchronized (Targets[targetNumber]) { Targets[targetNumber].notify(); } } }
The concept is simple: in our TargetNotify class, we are using an
array of objects for the sole purpose of using the wait and notify
mechanism. Instead of having all the threads wait on the
this
object, we choose an object to wait on.
(This is potentially confusing: we are not overriding the
wait()
method of the Object class here since
we’ve provided a unique signature.) Later, when we decide which
threads should wake up, we can target the notification since the
threads are waiting on different objects.
Whether the efficiency of a targeted notification scheme outweighs the extra complexity is the decision of the program designer. In other words, both techniques have their drawbacks, and we leave it up to the implementors to decide which mechanism is best.
Get Java Threads, Second Edition now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.