Why must wait and notify be called from synchronized block/method?

3.7k Views Asked by At

In the book I'm reading it says:

This technique is needed due to a race condition that would otherwise exist between setting and sending the notification and testing and getting the notification. If the wait() and notify() mechanism were not invoked while holding the synchronization lock, there would be no way to guarantee that the notification would be received.

Don't understand what this exactly means, why can the race condition happen?

EDIT: Hmmmm, I see now that this is possibly a duplicate question of Why must wait() always be in synchronized block , but it seams that the answers focus on making the condition check and going to wait synchronized.

Counterexample from shrini1000:

I can still do something like:
while(!condition) { synchronized(this) { wait(); } }
which means there's still a race between checking the condition and waiting even if wait() is correctly called in a synchronized block. So is there any other reason behind this restriction, perhaps due to the way it's implemented in Java?

2

There are 2 best solutions below

3
On

It must be all about the technique author must have presented before the article you have copied in question. I am not sure which book you are reading but I will try to answer this question.

I read a similar book "Thinking in Java" that talked about the same race condition. It suggests that this can be prevented using wait and notify so that the code doesn't miss the notify signal.

When two threads are coordinated using notify( )/wait( ) or notifyAll( )/wait( ), it’s possible to miss a signal. Suppose T1 is a thread that notifies T2, and that the two threads are implemented using the following (flawed) approach:

T1:

synchronized(sharedMonitor) {
    <setup condition for T2>
    sharedMonitor.notify();
}

T2:

while(someCondition) {
    // Assume that T2 evaluates someCondition and finds 
    // it true, now when program goes to next line thread
    // scheduler switches to T1 and executes notify again
    // after when control comes to T2 it blindly executes
    // wait(), but it has already missed notify so it will
    // always be waiting.

    .... some code ....

    synchronized(sharedMonitor) {
        sharedMonitor.wait();
    }
}

The (setup condition for T2) is an action to prevent T2 from calling wait( ), if it hasn’t already.

The solution is to prevent the race condition over the someCondition variable. Here is the correct approach for T2:

synchronized(sharedMonitor) {
    while(someCondition) {
        sharedMonitor.wait();
    }
}

0
On

The fact that something can be misused is hardly a counterexample.

Java only enforces that wait() and notify() are part of a synchronized block (since that's the only way they are supposed to be used), but it's up to you to define the block boundaries.

As a counter-counterexample, think about the finally block. Java only enforces that it comes after a try block, but you're the only one who should know what's supposed to go into that try block; you could even leave it empty (which would then miss the very point of finally).