Just as each object has a lock that can be obtained and released, each object also provides a mechanism that allows it to be a waiting area. And just like the lock mechanism, the main reason for this mechanism is to aid communication between threads.[3] The idea behind the mechanism is actually simple: one thread needs a certain condition to exist and assumes that another thread will create that condition. When this other thread creates the condition, it notifies the first thread that has been waiting for the condition. This is accomplished with the following methods:
- void wait()
Waits for a condition to occur. This is a method of the Object class and must be called from within a synchronized method or block.
- void notify()
Notifies a thread that is waiting for a condition that the condition has occurred. This is a method of the Object class and must be called from within a synchronized method or block.
What is the purpose of the wait and notify mechanism, and how does it work? The wait and notify mechanism is also a synchronization mechanism; however, it is more of a communication mechanism: it allows one thread to communicate to another thread that a particular condition has occurred. The wait and notify mechanism does not specify what the specific condition is.
Can wait and notify be used to replace the synchronized method? Actually, the answer is no. Wait and notify does not solve the race condition problem that the synchronized mechanism solves. As a matter of fact, wait and notify must be used in conjunction with the synchronized lock to prevent a race condition in the wait and notify mechanism itself.
Let’s use this technique to solve the timing problem in the
BusyFlag class. In our earlier version, the
getBusyFlag()
method would call
tryGetBusyFlag()
to obtain the
busyflag
. If it could not get the flag, it would
try again 100 milliseconds later. But what we are really doing is
waiting for a condition (a free busyflag
) to
occur. So we can apply this mechanism: if we don’t have the
condition (a free busyflag
), we
wait()
for the condition. And when the flag is
freed, we notify()
a waiting thread that the
condition now exists. This gives us the final, optimal implementation
of the BusyFlag class:
public class BusyFlag { protected Thread busyflag = null; protected int busycount = 0; public synchronized void getBusyFlag() { while (tryGetBusyFlag() == false) { try { wait(); } catch (Exception e) {} } } public synchronized boolean tryGetBusyFlag() { if (busyflag == null) { busyflag = Thread.currentThread(); busycount = 1; return true; } if (busyflag == Thread.currentThread()) { busycount++; return true; } return false; } public synchronized void freeBusyFlag() { if (getBusyFlagOwner() == Thread.currentThread()) { busycount--; if (busycount == 0) { busyflag = null; notify(); } } } public synchronized Thread getBusyFlagOwner() { return busyflag; } }
In this new version of the getBusyFlag()
method,
the 100-millisecond sleep is removed and replaced with a call to the
wait()
method. This is the wait for the required
condition to occur. The freeBusyFlag()
method
now contains a call to the notify()
method. This
is the notification that the required condition has occurred. This
new implementation is much better than the old one. We now
wait()
until the busyflag
is free—no more and no less—and we no longer waste CPU
cycles by waking up every 100 milliseconds to test if the
busyflag
is free.
There is another change: the getBusyFlag()
method is now synchronized. The getBusyFlag()
method was not synchronized in our earlier examples because the lock
scope would have been too large. It would not have been possible for
the freeBusyFlag()
method to be called while the
getBusyFlag()
method held the lock. However,
because of the way in which the wait()
method
works, there is no longer a danger of deadlock. The
wait()
method will release the lock, which will
allow other threads to execute the
freeBusyFlag()
method. Before the
wait()
method returns, it will reacquire the
lock, so that to the developer, it appears as if the lock has been
held the entire time.
What happens when notify() is called and there is no thread
waiting? This is a valid situation. Even with our BusyFlag
class, it is perfectly valid to free the
busyflag
when there is no other thread waiting
to get the busyflag
. Since the wait and notify
mechanism does not know the condition about which it is sending
notification, it assumes that a notification for which there is no
thread waiting is a notification that goes unheard. In other words,
if notify()
is called without another thread
waiting, then notify()
simply returns.
What are the details of the race condition that exists in
wait and notify?
In general, a thread that uses the
wait()
method confirms that a condition does not
exist (typically by checking a variable) and then calls the
wait()
method. When another thread sets the
condition (typically by setting that same variable), it then calls
the notify()
method. A race condition occurs
when:
The first thread tests the condition and confirms that it must wait.
The second thread sets the condition.
The second thread calls the
notify()
method; this goes unheard, since the first thread is not yet waiting.The first thread calls the
wait()
method.
How does this potential race condition get
resolved? This race condition is resolved by the
synchronization lock discussed earlier. In order to call
wait()
or notify()
, we must
have obtained the lock for the object on which we’re calling
the wait()
or notify()
method. This is mandatory: the methods will not work properly and
will generate an exception condition if the lock is not held.
Furthermore, the wait()
method also releases the
lock prior to waiting and reacquires the lock prior to returning from
the wait()
method. The developer must use this
lock to ensure that checking the condition and setting the condition
is atomic, which typically means that the condition is held in an
instance variable within the locked object.
Is there a race condition during the period that the wait()
method releases and reacquires the lock? The
wait()
method is tightly integrated with the
lock mechanism. The object lock is not actually freed until the
waiting thread is already in a state in which it can receive
notifications. This would have been difficult, if not impossible, to
accomplish if we had needed to implement the
wait()
and notify()
methods
ourselves. For our purposes, this is an implementation detail. It
works, and works correctly. The system prevents any race conditions
from occurring in this mechanism.
Why does the getBusyFlag()
method loop to test if the tryGetBusyFlag() method returns false?
Isn’t the flag going to be free when the wait() method
returns? No, the flag won’t necessarily be free when
the wait()
method returns. The race condition
that is solved internally to the wait and notify mechanism only
prevents the loss of notifications. It does not solve the following
case:
The first thread acquires the
busyflag
.The second thread calls
tryGetBusyFlag()
, which returnsfalse
.The second thread executes the
wait()
method, which frees the synchronization lock.The first thread enters the
freeBusyFlag()
method, obtaining the synchronization lock.The first thread calls the
notify()
method.The third thread attempts to call
getBusyFlag()
and blocks waiting for the synchronization lock.The first thread exits the
freeBusyFlag()
method, releasing the synchronization lock.The third thread acquires the synchronization lock and enters the
getBusyFlag()
method. Because thebusyflag
is free, it obtains thebusyflag
and exits thegetBusyFlag()
method, releasing the synchronization lock.The second thread, having received notification, returns from the
wait()
method, reacquiring the synchronization lock along the way.The second thread calls the
tryGetBusyFlag()
method again, confirms that the flag is busy, and calls thewait()
method.
If we had implemented the getBusyFlag()
method
without the loop:
public synchronized void getBusyFlag() { if (tryGetBusyFlag() == false) { try { wait(); tryGetBusyFlag(); } catch (Exception e) {} } }
then in step 10 the second thread would have returned from the
getBusyFlag()
method even though the
tryGetBusyFlag()
method had not acquired the
busyflag
. All we know when the
wait()
method returns is that at some point in
the past, the condition had been satisfied and another thread called
the notify()
method; we cannot assume that the
condition is still satisfied without testing the condition again.
Hence, we always need to put the call to the
wait()
method in a loop.
[3] Under Solaris or POSIX threads, these are often referred to as condition variables ; on Windows 95/NT, they are referred to as event variables.
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.