[Java - Synchronization] Wait() and Notify()

1) Explanation

wait() tells the calling thread to wait (possibly forever) until some other thread tells it to resume using the method, notify( ).
notify method is used, with wait(), to wake up threads that are waiting for an access to this object’s monitor. There are two ways to notify; notify() and notifyAll().

Notify() will wake up a randomly selected thread waiting on this object’s monitor.

Since notify() wakes up a single random thread it can be used to implement mutually exclusive locking where threads are doing similar tasks, but in most cases, it would be more viable to implement notifyAll().

NotifyAll() wakes all threads that are waiting on this object’s monitor.

The awakened threads will complete in the usual manner – like any other thread.

* There is a tricky part about notify methods, and that is, it only releases the lock after the methods following the notify method is completed as well (usually until it reaches the wait() statement). This will be explained more later.

1.1) More advanced details:

notify(): If the objects container house has multiple threads in waiting state then calling notify() on the this object given chance to one the thread to proceed. But after coming out of the waiting state the thread still has to contest for the object monitor and if it succeeds in getting the monitor it continue its execution otherwise thread will come back to waiting state. So notify() also has to be called from the synchronized block. If the notify() is not called from synchronized context then it throws IllegalMonitorStateException.

notifyAll(): Calling notifyAll() on the Object makes sure all the threads in Object container house are awakened but once awakened they have to compete with each other or any other thread wants to acquire the object monitor. Which ever thread succeeds continue its executions others have to go back to waiting state and settle in object container house. As notify(), notifyAll() should also be called in synchronized context.

2) Example Code




2.1) Explanation

First, we create a LinkedList that is shared amongst our consumerThread and producerThread. 
When both these threads start, (if consumerThread starts first) consumerThread will realize that it is empty and execute wait(), which frees up the lock for producerThread to access the synchronized block.
producerThread will fill up the LinkedList until it's full and execute wait(), which frees up the lock for consumerThread to consume.
And this goes on forever.

2.2) Expected Output


Que is Empty
Producing Value 2
Producing Value 52
Producing Value 87
Producing Value 39
Queue is Full
removing Element 2
removing Element 52
removing Element 87
removing Element 39
Que is Empty
....

2.3) If notify method releases the lock for the other thread, shouldn't the producer and consumer be constantly releasing the lock (rather than wait till the upper and lower limits are hit)?

The notify method truly releases the lock when it has completed its operations. Because we are putting the notify method in the while loop, it keeps looping until it hits the wait() method.
If we didn't put it in a while loop, then yes, the lock will be released after the code after notify method are completed.

2.4) Then, why can't we just use wait() if it has to reach wait() to truly release the lock?

Even though wait() does make the thread let go of the lock, it does not wake up the other thread to compete (or get) the lock.

2.5) Why is wait() in a while-loop?

Because notify() and notifyAll() randomly wakes up threads that are waiting on this object’s monitor, it’s not always important that the condition is met. Sometimes it can happen that the thread is woken up, but the condition isn’t actually satisfied yet.

We can also define a check to save us from spurious wakeups – where a thread can wake up from waiting without ever having received a notification.

Shouldn't the lock be released after the notify method?

3) Conclusion

Note that wait(), notify() and notifyAll() are low-level APIs - more traditional methods. They work well, but higher-level mechanism are often simpler and better – such as Java’s native Lock and Condition interfaces (available in java.util.concurrent.locks package).

Comments

Popular posts from this blog

[Redis] Redis Cluster vs Redis Sentinel

[Java - Synchronization] Semaphores and Mutex

[Unit Testing] Test Doubles (Stubs, Mocks....etc)