If we don't call wait() or notify() method from synchronized method/block, it will throw the "java.lang.IllegalMonitorStateException: current thread not owner".
For example, one thread read data from a buffer and one thread write data into the buffer. The reading data thread needs to wait until the writing data thread completely writes a block data into the buffer. The writing data thread needs to wait until the reading data thread completely read the data from the buffer.
If wait(), notify(), and notifyAll() methods can be called by an ordinary method, the reading thread calls wait() and the thread is being added to waiting queue. At the same moment, the writing thread calls notify() to signal the condition changes. The reading thread misses the change and waits forever. Hence, they must be called inside a synchronized method or block which is mutually exclusive.
There are few important points we should be aware of before going into the reasons for why wait(), notify() and notifyAll() must be called inside a synchronized method or block.
1. Every object created in Java has one associated monitor (mutually exclusive lock). Only one thread can own a monitor at any given time.
2. For achieving synchronization in Java this monitor is used. When any thread enters a synchronized method/block it acquires the lock on the specified object.
3. When any thread acquires a lock it is said to have entered the monitor. All other threads which need to execute the same shared piece of code (locked monitor) will be suspended until the thread which initially acquired the lock releases it.
4. wait() method tells the current thread (thread which is executing code inside a synchronized method or block) to give up monitor and go to waiting state.
notify method wakes up a single thread that is waiting on this object's monitor.
notifyAll method wakes up all the threads that called wait() on the same object.