Why should wait() always be called inside a loop
I have read that we should always call a wait()
from within a loop:
while (!condition) { obj.wait(); }
It works fine without a loop so why is that?
Solution 1:
You need not only to loop it but check your condition in the loop. Java does not guarantee that your thread will be woken up only by a notify()/notifyAll() call or the right notify()/notifyAll() call at all. Because of this property the loop-less version might work on your development environment and fail on the production environment unexpectedly.
For example, you are waiting for something:
synchronized (theObjectYouAreWaitingOn) {
while (!carryOn) {
theObjectYouAreWaitingOn.wait();
}
}
An evil thread comes along and:
theObjectYouAreWaitingOn.notifyAll();
If the evil thread does not/can not mess with the carryOn
you just continue to wait for the proper client.
Edit: Added some more samples. The wait can be interrupted. It throws InterruptedException and you might need to wrap the wait in a try-catch. Depending on your business needs, you can exit or suppress the exception and continue waiting.
Solution 2:
It's answered in documentation for Object.wait(long milis)
A thread can also wake up without being notified, interrupted, or timing out, a so-called spurious wakeup. While this will rarely occur in practice, applications must guard against it by testing for the condition that should have caused the thread to be awakened, and continuing to wait if the condition is not satisfied. In other words, waits should always occur in loops, like this one:
synchronized (obj) {
while (<condition does not hold>)
obj.wait(timeout);
... // Perform action appropriate to condition
}
(For more information on this topic, see Section 3.2.3 in Doug Lea's "Concurrent Programming in Java (Second Edition)" (Addison-Wesley, 2000), or Item 50 in Joshua Bloch's "Effective Java Programming Language Guide" (Addison-Wesley, 2001).
Solution 3:
Why should wait() always be called inside a loop
The primary reason why while
loops are so important is race conditions between threads. Certainly spurious wakeups are real and for certain architectures they are common, but race conditions are a much more likely reason for the while
loop.
For example:
synchronized (queue) {
// this needs to be while
while (queue.isEmpty()) {
queue.wait();
}
queue.remove();
}
With the above code, there may be 2 consumer threads. When the producer locks the queue
to add to it, consumer #1 may be blocked at the synchronized
lock while consumer #2 is waiting on the queue
. When the item is added to the queue and notify
called by the producer, #2 is moved from the wait queue to be blocked on the queue
lock, but it will be behind the #1 consumer which was already blocked on the lock. This means that the #1 consumer gets to go forward first to call remove()
from the queue
. If the while
loop is just an if
, then when consumer #2 gets the lock after #1 and calls remove()
, an exception would occur because the queue
is now empty -- the other consumer thread already removed the item. Even though it was notified, it needs to be make sure the queue
is for sure not empty because of this race condition.
This well documented. Here's a web page I created a while back which explains the race condition in detail and has some sample code.
Solution 4:
There might be more then just one worker waiting for a condition to become true.
If two or more worker get awake (notifyAll) they have to check the condition again. otherwise all workers would continue even though there might only be data for one of them.
Solution 5:
I think I got @Gray 's answer.
Let me try to rephrase that for newbies like me and request the experts to correct me if I am wrong.
Consumer synchronized block::
synchronized (queue) {
// this needs to be while
while (queue.isEmpty()) {
queue.wait();
}
queue.remove();
}
Producer synchronized block::
synchronized(queue) {
// producer produces inside the queue
queue.notify();
}
Assume the following happens in the given order:
1) consumer#2 gets inside the consumer synchronized
block and is waiting since queue is empty.
2) Now, producer obtains the lock on queue
and inserts inside the queue and calls notify().
Now,either consumer#1 can be chosen to run which is waiting for queue
lock to enter the synchronized
block for the first time
or
consumer#2 can be chosen to run.
3) say, consumer#1 is chosen to continue with the execution. When it checks the condition,it will be true and it will remove()
from the queue.
4) say,consumer#2 is proceeding from where it halted its execution (the line after the wait()
method). If 'while' condition is not there (instead an if
condition), it will just proceed to call remove()
which might result in an exception/unexpected behaviour.