Java › Concurrency & Threads
What is the difference between wait()/notify()/notifyAll() (Object's intrinsic monitor methods) and the newer java.util.concurrent tools, and why are the older methods considered error-prone?
IntermediateAnswer
wait()/notify()/notifyAll() are low-level primitives (must be called only while holding the associated object's monitor lock via synchronized, and wait() must always be called in a loop re-checking its condition due to the possibility of "spurious wakeups") for implementing custom condition-based thread coordination -- they're widely considered error-prone and difficult to use correctly (easy to miss the synchronized requirement, forget the while-loop condition check, or accidentally call notify() instead of notifyAll() and lose a waiting thread), which is why higher-level java.util.concurrent tools (CountDownLatch, Condition objects paired with ReentrantLock, BlockingQueue) are generally strongly preferred for virtually all modern coordination needs, reserving raw wait/notify largely for understanding how these higher-level tools are built internally.
// Error-prone raw wait/notify pattern (requires careful, easy-to-get-wrong discipline)
synchronized (lock) {
while (!conditionMet) { // MUST be a while loop, not if, due to spurious wakeups
lock.wait();
}
// proceed once condition is met
}
// Preferred modern alternative: BlockingQueue handles all this coordination internally
BlockingQueue<Task> queue = new LinkedBlockingQueue<>();
Task task = queue.take(); // blocks safely until an item is available, no manual wait/notify neededReal-world example A producer-consumer implementation initially hand-written with raw wait()/notify() (and a subtle bug from using notify() instead of notifyAll(), occasionally leaving a consumer thread stuck waiting forever) is refactored to use a simple BlockingQueue instead, eliminating an entire category of hard-to-diagnose coordination bugs by relying on a well-tested, higher-level abstraction.