wait, notify, notifyAll

This question is often framed within the context of a Producer-Consumer problem. For this problem, and for multithreading challenges in general, it is preferable to implement solutions using high-level synchronization primitives. Another approach is to use low-level but optimistic locking via compareAndSet. However, the use of notify/wait (pessimistic locking) is usually a specific requirement of the task, i.e. it is required to implement already existing BlockingQueue.

Along with synchronized, these methods constitute the lowest level of pessimistic locking in Java, used internally in the implementation of synchronization primitives. Since Java 5, there has been no need to use these methods directly, but theoretical knowledge of them is often still required in interviews.

To call these methods on an object, its monitor must be acquired, i.e., the call must be within a synchronized block on that object. Otherwise, an IllegalMonitorStateException is thrown. Therefore, a complete answer requires understanding how a monitor lock (synchronized block) works.

Calling wait pauses the current thread on this object and releases its monitor. Execution resumes when another thread calls notify and releases the monitor lock. If multiple threads are waiting on the object, notify wakes up a random one, while notifyAll wakes them all at once.

Theoretically, a wait can be interrupted without a notify call, at the discretion of the JVM (spurious wakeup). While this is extremely rare in practice, it's prudent to add another check for the condition to terminate the wait after calling wait.

Two other abnormal ways to end wait are external thread interruption and a timeout. An InterruptedException is thrown in case of an interruption. For a timeout, the wait time must be specified as a method parameter. A value of 0 will be ignored.

Various issues with implementing locks are discussed in Java Concurrency in Practice 14.1.3, 14.2.