Threads don't get suspended in groups. What happens is a thread enters a synchronized block or method, acquiring the lock (r or w here), and if the thread calls wait on the object whose lock it acquired then the thread is suspended, releases the lock it called wait on, and is added to the wait set for that lock.
There's a pattern here with a loop surrounding a call to wait. The thread calling the method has to keep waiting until the test in the loop is false. The state that lets the thread proceed from waiting is called the condition. The wait method is called in a loop mainly because the notified thread doesn't have ownership of the lock, it needs to test the current state once it re-acquires the lock.
You would wake up all the threads in a lock's wait set by calling notifyAll on that lock. In practice that's not optimal, because typically only one of the threads can acquire the lock and make progress at a time. Using notifyAll happens when threads contending for the same lock can be waiting for different conditions and a notification could be for a state that is irrelevant to some threads. If notify is used then only one thread (chosen at the whim of the scheduler) is woken up. If the condition that the thread is waiting on isn't what the notify was for then the notification is lost and no thread makes progress. With notifyAll if the notification is applicable to any of the threads then one of them can make progress. Which beats the alternative, even at the cost of all the other waiting threads getting context-switched and going back to waiting.
In the posted code the intention seems to be to avoid using notifyAll by having a separate lock object for each condition. The object r has threads waiting on it until the buffer is not empty, the object w has threads waiting on it until the buffer is empty. That way when notify is called it's sure to wake a thread that the notify is relevant to (only threads waiting to put can be woken up by w.notify()
).
The problem with this code is that the put and take operations acquire both locks, and they acquire them in the opposite order from each other. That's a really good way to cause a deadlock. With the synchronized keyword and intrinsic locks there is no way to timeout and back off, there's no good way to recover. Once you have the case where one thread has r and wants w, while another has w and wants r, then you're stuck. The two threads each holding a lock can't make progress, and any other threads can't get either lock, causing every thread trying to enter a method of this buffer to block until you kill the JVM.