5

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.

Oracle documentation: wait, notify, notifyAll

Evgenii
  • 81
  • 1
  • 5
  • 1
    "But maybe the `wait` method could take an object as an argument to know which object’s monitor to release. But instead what the `wait` method does is it waits on `this` and releases the monitor of `this`, no other options are given." - Eh? Exactly the object from which `wait` method is called is its argument. Calling `b.wait` would call the method of `b` object. – Tsyvarev Oct 07 '18 at 20:43
  • Re, "Synchronizing on the object would block it for other threads..." If thread A synchronizes on some object, o, that does _not_ prevent other threads from accessing or updating object o. The only thing it prevents is, it prevents othet threads from synchronizing on the same object, o, at the same time. – Solomon Slow Oct 07 '18 at 22:53
  • @Tsyvarev Clarified it in the question (see the added example) – Evgenii Oct 08 '18 at 03:16
  • @SolomonSlow Right, that's what I'm saying. – Evgenii Oct 08 '18 at 03:18
  • 1
    "So there is no need to synchronize on some voluntary object ... If it was allowed it would only cause unnecessary confusion." - I **believe** this is one of reason of the current API. Likely, it could be much more reasons ... But **we don't know** for sure. (It could be known only for Java developers, but not for other Stack Overflow community). For that reason questions like "why it is implemented so" are **badly suited** for Stack Overflow. Just use the existed API. And while your experience grows, you will find more reasons of that API ... or will be hired by Oracle and will change it :) – Tsyvarev Oct 08 '18 at 07:33
  • 1
    I think you are right. They want you to make sure that the change in the associated condition variable is properly communicated between the two threads. But having a lock on the monitor object is neither sufficient nor required for that (you can implement that condition variable however you want and it can be done correctly with another monitor). In retrospect we know this, just like we know that StringBuffer and Vector did not really need to have all their methods synchronized. But this kind of stuff was in Java from the very beginning, before we had more sophisticated concurrency tools. – Thilo Oct 08 '18 at 11:09
  • "before we had more sophisticated concurrency tools." Meaning that these days, you would not be using wait/notify directly, you'd use something more high-level from java.util.concurrency. – Thilo Oct 08 '18 at 11:12
  • ok, thanks. That's exactly what I am looking for: making sure that I don't miss anything important in my understanding of the subject and the reasoning is correct (makes sense). – Evgenii Oct 08 '18 at 13:06
  • Are you familiar with Oracle's "[Guarded Blocks Tutorial](https://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html)?" If not, maybe the answer you seek is there. – Solomon Slow Oct 08 '18 at 14:20
  • @SolomonSlow Haven't found the answer there. Thanks anyway. – Evgenii Oct 09 '18 at 12:56
  • 1) If the goal is to avoid the "lost wake-up problem", then one should make the waiting and notifying threads perform their "work" in a `synchronize(this)` block. That would be enough right? 2) In any case, usually, one should issue a timed wait(), as opposed to an indefinite one. In addition to "lost wake-ups", this approach also helps get over the irrational (but expected) behaviors on certain platforms – Ashutosh A Sep 15 '20 at 14:14
  • @Evgenii I don't understand this: Even if your proposed syntax existed, why would anyone wants to obtain the monitor of `b` and call `a.wait(b)`, where they could just obtain the monitor of `b` and call `b.wait()`? – Khoa Vo Mar 10 '22 at 05:50

0 Answers0