问题
Imagine you have a typical producer-consumer pattern in Java. To be a bit more efficient you want to use notify()
and not notifyAll()
when a new element is added to the queue. If two producer threads invoke notify, is it guaranteed that two distinct waiting consumer threads will be awoken? Or can it be that two notify()
s fired shortly after each other cause the same comsumer thread to be queued for wakeup twice? I can't find the section is the API describing how this exactly works. Does java have some atomic internal operation for waking up threads exactly once?
If only one comsumer is waiting then the second notify would be lost, that is no problem.
回答1:
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.
回答2:
Yes, What you describe can happen.
As described in the javadoc, notify
wakes an arbitrary thread. So if your thread is finished and has called wait
before the next notify
, then it is one of the arbitrary candidates for waking.
I have extensive experience with multithreaded applications, and I find that I use one of these two patterns:
There are multiple sleeping threads which need to wake up on an event, and the order in which they wake doesn't matter. In this case, I use
notifyAll
to wake them.There is one sleeping thread which needs to wake up on an event. In this case, I use
notify
to wake it.
If I ever have a situation where there are multiple sleeping threads and I want to only wake one of them, I use a different design to achieve this. Basically, I build something out myself so that there is no arbitrary decision being made by the runtime environment. I always want to know exactly what will wake.
I break down a design into one of these two scenarios, or I use something from the java.util.concurrent package. I never have problems with spurious notifications, but I am also very careful on the objects I use for locking. I tend to create vanilla Object
instances whose only purpose is to be the target of a lock operation, but occasionally I'll use an object whose class type is well-defined and under my control.
回答3:
From the javadoc for notify():
The choice of which thread to notify "is arbitrary and occurs at the discretion of the implementation"
It will almost certainly not be a "fair" (as the term is used in computer science and parallelism) algorithm for waking threads. It's entirely possible that the same thread is woken up twice in quick succession. Also note that spurious notifications are also possible.
In general, I agree with the comments that suggest using a BlockingQueue implementation instead of doing this yourself.
回答4:
You can use ReentrantLock to obtain a fair arrival-order policy. The interface ReadWriteLock, you obtain the producer-consumer behavior. The class ReentrantReadWriteLock combine both capabilities.
Or
You should use ArrayBlockingQueue, where this pattern is already implemented.
int capacity = 10;
boolean fair = true;
new ArrayBlockingQueue(capacity, fair);
来源:https://stackoverflow.com/questions/10684111/can-notify-wake-up-the-same-thread-multiple-times