why wait/notify/notifyAll methods are not synchronized in java ?
Asked Answered
H

7

23

in Java whenever we need to call wait/notify/notifyAll, we need to have access to object monitor (either through synchronized method or through synchronized block). So my question is why java didn't go for synchronized wait/notify methods removing the restriction of calling these methods from synchronized block or methods.

In case these are declared as synchronized, it would have automatically taken the monitor access.

Hartley answered 11/8, 2011 at 1:9 Comment(0)
E
11

For notify and notifyAll, the problem with your idea is that when you notify you also have other stuff you're typically doing in the same synchronized block. So making the notify method synchronized wouldn't buy you anything, you'd still need the block. Likewise wait has to be in a synchronized block or method in order to be useful, such as being inside a spinlock where the test has to be synchronized anyway. So the granularity of locking is all wrong for what you suggest.

Here's an example, this is about the simplest queue implementation you can have in Java:

public class MyQueue<T> {

    private List<T> list = new ArrayList<T>();

    public T take() throws InterruptedException {
        synchronized(list) {
            while (list.size() == 0) {
                list.wait();
            }
            return list.remove(0);
        }
    }

    public void put(T object) {
        synchronized(list) {
            list.add(object);
            list.notify();
        }
    }
}

So you can have producer threads that add things to the queue and consumer threads that take things out. When a thread goes to get something out of the queue it needs to check within the synchronized block that there's something in the list, and once it's been notified it needs to reacquire the lock and make sure there is still something in the list (because some other consumer thread could have stepped in and grabbed it).There's also the "spurious wake-up" phenomenon: you can't rely on getting woken up as sufficient evidence that something happened, you need to check that whatever condition you're waiting for is actually true, and that needs to be done within the synchronized block.

In both of these cases, checks surrounding the wait need to be made with the lock held so that when the code takes action based on those checks it knows that those results are currently valid.

(If your use case does not have state that needs changing as described above, synchronized is probably the wrong tool for the job. Using some other means, such as CountdownLatch, may give you a simpler solution.)

Entire answered 11/8, 2011 at 1:50 Comment(6)
Not sure I agree that you'd always need synchronized blocks anyways. Consider an application with a "Download" thread and a "Print" thread, and a single lock object. The "Download" thread just downloads data from a url, and calls notify() on the lock when it is done, and the "Print" thread just calls wait() on the lock object when it starts, and prints "Download finished" once the wait() returns. None of this strictly requires the addition of synchronized blocks in either thread.Griseofulvin
@Nathan Yes, most of the time your wait is inside some condition checking loop: while(conditionVar) {... o.wait() ...} where the conditionVar is set by some other thread, which also fires the notify. You get in serious trouble if the second thread does its business when the original thread has checked the conditionVar, but has not yet started waiting. This does not happen if the whole while loop is in the synchronized block (no race condition).Pallas
@Griseofulvin due to spurious wake up, you have to re-check condition after wait(). since the condition is touched concurrently, that means wait() must be inside a bigger synchronized block.Agrapha
@Agrapha - You don't have to, you're simply encouraged to do so. The documentation even mentions that although spurious wake-up may technically occur, it really should not actually happen in a typical application.Griseofulvin
@Griseofulvin "While this will rarely occur in practice, applications must guard against it". Another form of fruitless wakeup is by notifyAll when all waiters are woken, but only the first one can do anything; others must go back to wait. Anyway, because wait released lock, it's quite dangerous not to double check condition when wait returns.Agrapha
@Griseofulvin a more serious problem is checking condition before entering wait. it must be done, and must be done atomically ( check and wait under the same lock). otherwise you can miss notify(), and the waiter never wakes up. I don't believe there is a use case where a "naked wait" can be used correctly.Agrapha
G
7

Good question. The comments in the JDK7 Object implementation shed some light on this, I think (emphasis mine):

This method causes the current thread (call it T) to place itself in the wait set for this object and then to relinquish any and all synchronization claims on this object.

...

The thread T is then removed from the wait set for this usual manner with other threads for the right to synchronize on the object; once it has gained control of the object, all its synchronization claims on the object are restored to the status quo ante - that is, to the situation as of the time that the wait method was invoked. Thread T then returns from the invocation of the wait method. Thus, on return from the wait method, the synchronization state of the object and of thread T is exactly as it was when the wait method was invoked.

So I think the first point to note is that wait() doesn't return until the caller is done waiting (obviously). Meaning that if wait() itself were synchronized then the caller would continue to hold the lock on the object, and nobody else would be able to wait() or notify().

Now obviously wait() is doing something tricky behind the scenes to force the caller to lose its ownership of the lock on the object anyways, but perhaps this trick would not work (or would be significantly more difficult to make work) if wait() itself were synchronized.

The second point is that if multiple threads are waiting on an object, when notify() is used to wake exactly one of them the standard contention method is used to allow only one thread to synchronize on the object, and that wait() is supposed to restore the caller's synchronization claims to the exact state they were in prior to the call to wait(). It seems possible to me that requiring the caller to hold the lock prior to calling wait() simplifies this, as it removes the need to check to see if the caller should or should not continue holding the lock after wait() returns. The contract stipulates that the caller must continue holding the lock, and thus some of the implementation is simplified.

Or perhaps it was simply done to avoid the appearance of the logical paradox of "if wait() and notify() are both synchronized, and wait() doesn't return until notify() is called, how can either ever be used successfully?".

Those are my thoughts, anyways.

Griseofulvin answered 11/8, 2011 at 1:44 Comment(3)
+1 Good answer. FYI: I fixed the code formatting in the javadoc quote.Largeminded
incorrect. there is no problem with synchronized(obj){ obj.wait(); }Agrapha
@Agrapha - Huh? I never said there was any sort of problem with synchronized(obj){ obj.wait(); }. I'm suggesting ideas as to why the synchronized(obj) bit is required. Nothing more.Griseofulvin
L
2

My guess would be that the reason the synchronized block is required is that the use of wait() or notify() as the only action in a synchronized block is almost always a bug anyway.

Findbugs even has a warning for this, which it calls a "naked notify".

Largeminded answered 11/8, 2011 at 1:53 Comment(1)
The naked notify warning is quite valid - it implies that one ought to be using the plain old mutual exclusion model.Topographer
A
2

Among all the non-buggy codes I've read and written, all the them use wait/notify in a bigger synchronization block involving read/write of other conditions

synchronized(lock)
    update condition
    lock.notify()

synchronized(lock)
    while( condition not met)
        lock.wait()

If wait/notify are themselves synchronized, no harm is done to all the correct codes (may a small performance penalty); it won't do any good either to all the correct codes.

However, it would have permitted and encouraged a lot more incorrect codes.

Agrapha answered 11/8, 2011 at 14:7 Comment(0)
S
1

Someone more experienced with multithreading should feel free to step in, but I believe this would remove the versatility of synchonized blocks. The point of using them is to synchronize on a particular object which functions as the monitored resource/semaphore. wait/notify methods are then used to control execution flow within the synchronized block.

Note that synchronized methods are shorthand for synchronizing on this for the duration of the method (or the class for static methods). Synchronizing wait/notify methods themselves would remove the point of their use as stop/go signals between threads.

Subsidence answered 11/8, 2011 at 1:33 Comment(0)
T
1

The wait-notify model of synchronization requires that you acquire the monitors on an object first, before proceeding to perform any work. It is different from the mutual exclusion model that is used by a synchronized block.

The wait-notify or mutual cooperation model is typically used in a producer-consumer scenario where one thread produces events that are consumed by another thread. Well-written implementations will strive to avoid the scenarios where a consumer is starved or the producer overruns the consumer with too many events. To avoid this, you would use the wait-notify protocol where

  • the consumer waits for the producer to produce an event.
  • the producer produces the event and notifies the consumer, and then usually goes to sleep, until it is notified by the consumer.
  • when the consumer is notified of an event, it wakes up, processes the event and the notifies the producer that it has finished processing the event.

You could have many producers and consumers in this scenario. Acquiring the monitor through the mutual exclusion model, on wait, notify or notifyAll is bound to destroy this model, since the producer and the consumer do not perform the waiting explicitly. The underlying threads will be present in either the wait set (used by the wait-notify model) or the entry set (used by the mutual exclusion model) of a monitor. Invoking notify or notifyAll signals thread(s) to be moved from the wait set to the entry set of the monitor (where there may be contention for the monitor, among several threads, and not just the recently notified one).

Now, when you want to automatically acquire the monitor on wait, notify and notifyAll using the mutual exclusion model, it is usually an indication that you do not need to be using the wait-notify model. This is by inference - you would typically signal other threads, only after doing some work in one thread, i.e. on a change in state. If you are automatically acquiring the monitor and invoking notify or notifyAll, you are merely moving threads from the wait set to the entry set, without any intermediate state in your program, implying that the transition is unnecessary. Quite obviously, the authors of the JVM were aware of this, and have not declared the methods as synchronized.

You can read more about the wait sets and entry sets of monitors in Bill Venner's book - Inside the Java Virtual Machine.

Topographer answered 11/8, 2011 at 2:22 Comment(0)
B
0

I think wait without synchronized can work good in some scenario. But it can't be used for complicated scenario without race condition, "spurious wakeups" might occur.

Code works for the queue.

// producer
give(element){
  list.add(element)
  lock.notify()
}

// consumer
take(){
  obj = null;
  while(obj == null)
    lock.wait()
    obj = list.remove(0) // ignore error indexoutofrange
  return obj
}

This code haven't explain the state of shared data, it will ignore the last element and may not work in mutil-thread condition. Without race condition, this list in 1, 2 may have totally different states.

// consumer
take(){
  while(list.isEmpty()) // 1
    lock.wait()
  return list.remove(0) // 2
}

And now, makes it a little more complicated and obvious.

execution of instruction

  • give(element) lock.notify()->take() lock.wait() resurrected->take() list.remove(0)->rollback(element)
  • give(element) lock.notify()->take() lock.wait() resurrected->rollback(element)->take() list.remove(0)

"spurious wakeups" occur, also makes code unpredicatable.

// producer
give(element){
  list.add(element)
  lock.notify()
}
rollback(element){
  list.remove(element)
}

// business code 
produce(element){
   try{
     give(element)
   }catch(Exception e){
     rollback(element) // or happen in another thread
   }
}

// consumer
take(){
  obj = null;
  while(obj == null)
    lock.wait()
    obj = list.remove(0) // ignore error indexoutofrange
  return obj
}

Reference of Chris Smith Reference of insidevm

Bearded answered 21/4, 2018 at 3:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.