I am learning about cooperation between concurrent tasks and I have got that question and a possible answer to it. I would like to make sure I understand it correctly.
So to call a.wait()
it is first required to synchronize on the object a
or to be more precise a thread must become the owner of the a
's monitor. As far as I understand, the only reason why a.wait()
should be called in a synchronized context (together with a.notify()
/a.notifyAll()
) is to avoid a race condition/The Lost Wake-Up Problem. But theoretically, it is possible to avoid the race condition calling a.wait()
,a.notify()/a.notifyAll()
by synchronizing on some other object, like this:
Thread #1:
synchronized(b) {
…
a.wait(); // here a thread owns only one monitor: the monitor of the object stored in the variable b, where a != b
…
}
Thread #2:
synchronized(b) {
…
a.notify(); // the same here
…
}
The expected behavior is: the thread #1 acquires the monitor of b
, enters the critical section, invokes a.wait()
, then waits on a
and releases the monitor of b
. Afterwards the thread #2 acquires the monitor of b
, enters the critical section, invokes a.notify()
(the thread #1 gets notified), exits the critical section and releases the monitor of b
, then the thread #1 acquires the monitor of b
again and continues running. Thus, only one critical section synchronized on b
can run at a time and no race condition can occur. You may ask, “but how would wait()
know that the object b
is used in this case to avoid the race condition and thus its monitor must be released?”. Well, that way it wouldn’t know. But maybe the wait
method could take an object as an argument to know which object’s monitor to release, for example, a.wait(b)
, to wait on a
and release the monitor of b
. But there is no such an option: a.wait()
causes a thread to wait on a
and releases the monitor of exactly the same a
. Period. The invocation of the wait
and notify
methods in the code above results in IllegalMonitorStateException.
According to this information from the Wikipedia article, the functionality of the wait
, notify/notifyAll
methods is built into every object's monitor where it is integrated into the functionality of the synchronization mechanism at the level of every individual object. But still it doesn't explain why it was done this way in the first place.
My answer to the question: before calling the wait method of an object, a thread should own the monitor of exactly the same object because to avoid a race condition this object is always not only a viable option but the best one.
Let’s see if there are any situations where it could be undesirable to synchronize on an object before invoking its wait
, notify/notifyAll
methods (plus some relevant logic). Synchronizing on the object would block it for other threads which could be undesirable if the object has some other synchronized methods (and/or there are some critical sections synchronized on the object) having nothing to do with the waiting logic. In that case it is always possible to refactor the corresponding class(es) so that the wait
and notify/notifyAll
methods operate on one object, while other synchronized methods/blocks operate on another one(s). For example, one of possible solutions could be creating a dedicated object specifically for a waiting logic (to wait and synchronize on) like in the example below:
public class A {
private Object lock = new Object(); // an object to synchronize and wait on for some waiting logic
private boolean isReady = false;
public void mOne() throws InterruptedException {
synchronized(lock) { // it blocks the instance of the Object class stored in the variable named lock
while(!isReady) {
lock.wait();
}
}
}
public void mTwo() {
synchronized(lock) { // the same here
isReady = true;
lock.notify();
}
}
synchronized public void mThree() { // it blocks an instance of A
// here is some logic having nothing to do with the above waiting and
// mThree() can run concurrently with mOne() and mTwo()
}
}
So there is no need to synchronize on some voluntary object to avoid a certain race condition regarding calling wait
, notify/notifyAll
methods. If it was allowed it would only cause unnecessary confusion.
Is it correct? Or maybe I am missing something here. I would like to make sure I don't miss anything important.