My answer has some implementation specific information. It is based on my working knowledge of Sun JVM and other thread library behavior.
If two producer threads invoke notify, is it guaranteed that two distinct waiting consumer threads will be awoken?
No it is not. There is no guarantee that there will be any consumers awoken. What is guaranteed is that if there are 2 threads that are waiting, then 2 different threads will be put into the run queue.
Or can it be that two notify()
s fired shortly after each other cause the same comsumer thread to be queued for wakeup twice?
No. Two notify()
calls will not cause the same consumer thread to be queued twice. It may however, cause one thread to be awoken and there may not be other threads waiting, so the second notify()
call may do nothing. Of course the thread could have been awoken and then gone right back waiting again and so get the second notify()
call that way, but I don't think think that is what you are asking.
Does java have some atomic internal operation for waking up threads exactly once?
Yes. The Thread
code has a number of synchronization points. Once a thread has been notified it is moved out of the wait
queue. Future calls to notify()
will look into the wait
queue and not find the thread.
One more important point. With producer/consumer models, always make sure you are testing the condition in a while
loop. The reason is that there is race conditions with consumers that are blocked on the lock but not waiting on the condition.
synchronized (workQueue) {
// you must do a while here
while (workQueue.isEmpty()) {
workQueue.wait();
}
workQueue.remove();
}
Consumer1
could be waiting on workQueue
. Consumer2
could be blocked at the synchronized
but in the run-queue. If something is put into the workQueue
and workQueue.notify()
is called. Consumer2
is put into run-queue now but is behind Consumer1
who is there first. This is a common implementation. So Consumer1
goes in a removes the item from the workQueue
that Consumer2
was notified about. Consumer2
has to test again if the workQueue
is empty otherwise remove()
will throw because the queue is empty again. See here for more details of the race.
It is also important to realize that spurious wakeups have been documented so the while
loop protects against a thread being awoken without a wait()
call.
All this said, if you can reduce your producer/consumer code by using a BlockingQueue
as recommended in other answers then you should do so. The BlockingQueue
code already has solved all of these issues.