29

I am in doubt with that , in Java language, we need to acquire the lock, before we await some condition to be satisfied.

For example, int java monitor lock:

synchronized(lock){
   System.out.println("before lock ...");
   lock.wait();
   System.out.println("after lock ...");
}

or the concurrency utils:

Lock lock = new ReentrantLock();
Condition cond = lock.newCondition();

lock.lock();
try{
     System.out.println("before condition ...");
     cond.await();
     System.out.println("after condition ...");
}catch(Exception e){
     e.printStackTrace();
}finally{
     lock.unlock();
}

So, why we can't await, without hold the lock ?

Does other languages differ, or it's just in Java?

I hope you can explain the reason after the design, but not only for JAVA-SPEC definition.

5
  • 2
    Java runs in the context of a virtual machine, and the behavior of the virtual machine is specified. Commented Sep 25, 2015 at 3:57
  • so, that's because of SPEC?
    – Chinaxing
    Commented Sep 25, 2015 at 4:01
  • 2
    Well of course it is because of the specifcation. Strange question.
    – user207421
    Commented Sep 25, 2015 at 4:02
  • stackoverflow.com/q/7019745
    – ZhongYu
    Commented Sep 25, 2015 at 5:35
  • Because that is POSIX requirement. JVM is based on it. stackoverflow.com/a/52384541/851185
    – jean
    Commented Sep 19, 2018 at 2:17

6 Answers 6

31

Imagine you have something that a thread might need to wait for. Maybe you have a queue and a thread needs to wait until there's something on the queue so it can process it. The queue must be thread-safe, so it has to be protected by a lock. You might write the following code:

  1. Acquire the lock.
  2. Check if the queue is empty.
  3. If the queue is empty, wait for the something to be placed on the queue.

Oops, that won't work. We hold the lock on the queue so how can another thread place something on it? Let's try again:

  1. Acquire the lock.
  2. Check if the queue is empty.
  3. If the queue is empty, release the lock and wait for the something to be placed on the queue.

Oops, now we still have a problem. What if after we release the lock but before we wait for something to be placed on the queue, something is placed on the queue? In that case, we will be waiting for something that already happened.

Condition variables exist to solve this exact problem. They have an atomic "unlock and wait" operation that closes this window.

So await must hold the lock because otherwise there would be no way to ensure you weren't waiting for something that already happened. You must hold the lock to prevent another thread from racing with your wait.

2
  • In java, condition or object.wait only be wakeup by after notify or signal. So, your explain is right. I am think that if we can let the condition's semantic like this : when wait/await, check its wakeup bit( which was set by notify/signal), if that true, just quit wait/await(instead of keeping wait). and for the notify/signal thread, just set the condition wakeup bit when do notify/signal, which means the condition is satisfied. if design like this, so the lock acquire is not needed ?
    – Chinaxing
    Commented Sep 27, 2015 at 9:08
  • 1
    @Chinaxing No, don't do that. Consider: Two threads are blocked on the condition variable. A thread puts an object on the queue, signals the condition variable and wakes one thread. A thread puts an object on the queue, signals the condition variable and wakes the other thread. The first woken thread processes the first item on the queue. Then, before the second thread can execute, the first woken thread processes the second item on the queue. Now the second thread executes, the condition variable has been signaled, but the queue is empty. Commented Sep 27, 2015 at 18:46
10

Well, what are we waiting for? We are waiting for a condition to become true. Another thread will make the condition true, then notify the waiting threads.

Before entering wait, we must check that the condition is false; this check and the wait must be atomic, i.e. under the same lock. Otherwise, if we enter the wait while the condition is already true, we'll likely never wakeup.

Therefore it is necessary that the lock is already acquired before calling wait()

synchronized(lock)
{
    if(!condition)
        lock.wait();

If wait() automatically and silently acquires lock, a lot of bugs will go undetected.


Upon wakeup from wait(), we must check the condition again -- there's no guarantee that the condition must become true here (for lots of reasons - spurious wakeup; timeout, interruption, multiple waiters, multiple conditions)

synchronized(lock)
{
    if(!condition)
        lock.wait();
    if(!condition)   // check again
        ...

Typically, if the condition is still false, we'll wait again. Therefore the typical pattern is

    while(!condition)
        lock.wait();

But there are also cases where we don't want to wait again.


Could there ever be legit use cases where naked wait/notify make sense?

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

Sure; an application can be made up with naked wait/notify, with well defined behavior; argument can be made that this is the desired behavior; and this is the best implementation for that behavior.

However, that is not the typical usage pattern, and there is no reason to account for it in API design.

1
  • 1
    Well, even with a “naked wait”, we have the problem that the wait will only return if another thread calls notify after the wait has started. But without both threads being synchronized on the same object, there isn’t even an ordering relationship. It should be explicitly noted that not only there is no guaranty that the condition became true, there is also no guaranty that the condition didn’t become false again between the notification and the wakeup.
    – Holger
    Commented Sep 25, 2015 at 12:25
2

See the doc for Condition.

A Condition is like a wait pool or wait set of an object and it replaces the use of the Object monitor methods (wait, notify and notifyAll). Conditions enable one thread to suspend execution (to "wait") until notified by another thread that some state condition may now be true. A Condition instance is intrinsically bound to a lock just like the Object monitor methods require the lock of the shared object to wait or notify on. So before invoking await() on a condition, the thread must have locked the Lock object that is used to produce the condition. When the await() method is invoked, the lock associated with the condition is released.

1

If the thread were merely waiting for a signal to proceed there are other mechanisms for doing that. Presumably there is some state protected by the lock that the thread is waiting to be operated on and satisfy some condition. To properly protect that state the thread should have the lock before and after waiting for the condition, so it makes sense to require acquisition of the lock.

0

a sounds-reasonable answer

It is a JVM thing. An Object x has:

  • an Entry Set: a queue for threads attempting to synchronized(x)

  • a Waiting Set: a queue for threads called x.wait()

When you call x.wait(), JVM adds your current thread into Waiting Set; when you call x.notify()/x.notifyAll(), JVM removes one/all element from Waiting Set.

Multiple threads may call x.wait()/x.notify()/x.notifyAll() to modify the Waiting Set. In order to ensure the Waiting Set thread safety, JVM accepts only one operation from one thread at one time.

-2

Simple answer is because otherwise you will get IllegalMonitorStateException which is specified in Object.wait javadoc. Internally, synchronization in Java uses underlying OS mechanizm. So it is not only Java.

1
  • 2
    Yeah, but the OP wants to know why you get the IllegalMonitorStateException. Commented Sep 25, 2015 at 13:43

Not the answer you're looking for? Browse other questions tagged or ask your own question.