Ответ 1
Это неправильно:
synchronized(foo) {
foo.wait();
}
Проблема в том, что будет пробуждать эту тему? То есть, как вы гарантируете, что другой поток не вызовет foo.notify()
, прежде чем первый поток вызовет foo.wait()
? Это важно, потому что объект foo не будет помнить, что он был уведомлен о том, что сначала будет вызван вызов уведомления. Если есть только одно уведомление(), и если это произойдет до wait(), то wait() никогда не вернется.
Здесь, как предполагалось использовать wait и notify:
private Queue<Product> q = ...;
private Object lock = new Object();
void produceSomething(...) {
Product p = reallyProduceSomething();
synchronized(lock) {
q.add(p);
lock.notify();
}
}
void consumeSomething(...) {
Product p = null;
synchronized(lock) {
while (q.peek() == null) {
lock.wait();
}
p = q.remove();
}
reallyConsume(p);
}
В этом примере наиболее важными являются то, что существует явный тест для условия (т.е. q.peek()!= null), и что никто не изменяет условие без блокировки блокировки.
Если потребитель вызывается первым, тогда он найдет очередь пустой и будет ждать. Не может быть момента, когда производитель может проскользнуть, добавить Продукт в очередь и затем уведомить блокировку, пока потребитель не будет готов принять это уведомление.
С другой стороны, если производитель сначала вызывается, то пользователю гарантированно не называть wait().
Цикл в потребителе важен по двум причинам: во-первых, если есть более одного потребительского потока, то один потребитель может получать уведомление, но затем другой потребитель пробирается и крадет Продукт из очередь. Единственная разумная вещь для кулачного потребителя в этом случае - снова подождать для следующего Продукта. Другая причина, по которой этот цикл важен, заключается в том, что Javadoc говорит, что Object.wait() разрешено возвращать, даже когда объект не был уведомлен. Это называется "ложным пробуждением", и правильный способ справиться с ним - вернуться и снова подождать.
Также обратите внимание: блокировка private
и очередь private
. Это гарантирует, что никакая другая единица компиляции не будет вмешиваться в синхронизацию в этом модуле компиляции.
И обратите внимание: блокировка - это другой объект из самой очереди. Это гарантирует, что синхронизация в этом модуле компиляции не будет мешать любой синхронизации, выполняемой реализацией Queue (если есть).
ПРИМЕЧАНИЕ. В моем примере повторно изобретено колесо, чтобы доказать точку. В реальном коде вы должны использовать методы put() и take() для ArrayBlockingQueue, которые будут заботиться обо всем ожидании и уведомлении для вас.