I have been playing with my own version of this, using \'if\', and all seems to be working fine. Of course this will break down horribly if signalAll() is used instead of s
The main reason for this is that when a thread is notified it can't act right away, it has to acquire the lock first. And there is no guarantee it will be the next thread to get the lock.
When a thread receives a notification while waiting, it does not have the lock. (It released the lock when it started waiting.) Before it can leave the wait method it has to reacquire the lock. It has no priority over any other thread contending for the lock, it is entirely likely some other thread may get there first.
Whatever those intervening threads do could change the state of the object being accessed, possibly making the notification irrelevant. So once the notified thread has successfully reacquired the lock, it needs to check the condition again in order to verify that the condition that it was notified about is still actually true. The while loop is there so that the thread can check the condition again once it has the lock.
In addition to that there are other reasons to use a while loop for check that the condition you're waiting for is present:
It's not valid to infer from your thread having stopped waiting that it necessarily must have gotten notified; this is the "spurious wakeup".
Also if there are multiple things that a notification could mean, then you'd want to check the condition again in order to make sure the event you're interested in was what you were woken up for.
Why does java.util.concurrent.ArrayBlockingQueue use 'while' loops instead of 'if' around calls to await()?
They use while
instead of if
to protect against the classic thread race conditions in producer/consumer models and to protect against the much more rare case of spurious wakeups.
When (for example) multiple consumers are waiting for a particular condition (like a queue is empty) and the condition gets notified, there is a chance that another thread may lock first and "steal" the item added to the queue. The while
loop is necessary for the thread to make sure that the queue has an item before it tries to de-queue it.
I've written some sample code and more documentation which demonstrates the race condition.
Looking at your specific code, the race is as follows:
await()
in the while loop, waiting for there to be items in the queueget()
, locks on the queue, and has to wait for #2 to unlock (it is NOT waiting on hasItems
but it is waiting to get the lock
)hasItems.signal()
to notify someone that there is an item thereif
statement, it would go forward and try to dequeue from an empty list which would throw ArrayIndexOutOfBoundsException
or something.The reason why the while
statement is necessary is to handle these race conditions. In step 8 above, with a while
, thread #1 would instead loop around back to the test to see if there are items in the queue, finds out that there are none, and then goes back to waiting.
This is a classic issue that trips up a lot of reentrant programmers. The initial versions of the O'Reilly pthreads bible, for example, had example code without the while loop and had to be republished.
With some thread systems, it is easier for the system to awaken all conditions instead of the specific condition that has been signaled so a "spurious wakeup" can occur. The while
loops protect against this as well.
Another problem with your implementation is a potentially lost "signal". if you look closely at the jdk impls which handle InterruptedException, some of them signal before returning. this is because a thread could be chosen to be signalled and then end up getting interrupted, in which case that signal would be lost. thus, when interrupted, the jdk impls re-signal before bailing out. since this thread may or may not have actually been receiving a signal, this may also cause a "spurious wakeup" (as previously mentioned).
To protect against spurious wake ups. There is no guarantee made to you by the JVM that the only possible reason the thread will ever start running again is because you called signal in the way you intended. Sometimes it will just get started accidentally and go (Spurious wake up). So you have to keep waiting again if the condition you want to run on isn't actually true.
This is explained in the javadoc for the wait method: http://java.sun.com/javase/6/docs/api/java/lang/Object.html#wait%28long%29
And mentioned in the docs for await: http://java.sun.com/javase/6/docs/api/java/util/concurrent/locks/Condition.html#await%28%29
The lock associated with this Condition is atomically released and the current thread becomes disabled for thread scheduling purposes and lies dormant until one of four things happens:
Some other thread invokes the signal() method for this Condition and the current thread happens to be chosen as the thread to be awakened; or
Some other thread invokes the signalAll() method for this Condition; or
Some other thread interrupts the current thread, and interruption of thread suspension is supported; or
* A "spurious wakeup" occurs.
Some implementations of the Condition interface may suppress spurious wakeups, but relying on that would hence be relying on an implementation detail and makes your code unportable.
Perhaps missing your point, but the original code uses a while instead of if because there maybe multiple thread listening/consuming the queue...