#java #synchronization #blocking #monitor #java.util.concurrent
#java #синхронизация #блокировка #монитор #java.util.concurrent
Вопрос:
Я пишу BlockingQueue и задаюсь вопросом, как другие реализации решают эту проблему:
Если у меня есть только один монитор (объект очереди) и разрешаю производителям и потребителям wait
, я должен буду убедиться, что notifyAll
вместо notify
этого вызывается, иначе производитель может только сигнализировать другому ожидающему производителю о продолжении, даже если очередь заполнена. Заставляя потребителя ждать, даже несмотря на то, что товар доступен. С другой стороны, вызов notifyAll
, похоже, не является масштабируемым решением для многих потоков и процессоров.
BlockingQueue
Используют ли s два монитора? Один из них был ожиданием производителей, а другой — ожиданием потребителей? Затем мне пришлось бы синхронизировать очередь и релятивный монитор инкапсулированным образом. Это правильный путь?
Комментарии:
1. Я полагаю, вы уже ознакомились с реализациями блокирующих очередей в Java ?
2. Спасибо, нет, я могу это увидеть?
3. Загрузите и установите любой Java SDK > = 5.0 и найдите файл «src.zip » который содержит исходный код стандартного Java API. При использовании eclipse вы можете прикрепить этот файл как «исходный файл» к «rt.jar » библиотека для удобной навигации и доступа к исходным текстам и JavaDoc.
Ответ №1:
Я не уверен, как это делается BlockingQueue
, но одним из возможных решений является использование ReentrantLock
вместо synchronized
.
Он имеет ту же семантику, syncrhonized
что и, но обеспечивает некоторые улучшения. В частности, у него может быть несколько условий, которые другие потоки могут wait
:
public class MyBlockingQueue<E> {
private Lock lock = new ReentrantLock();
private Condition notEmpty = lock.newCondition();
private Condition notFull = lock.newCondition();
public void put(E e) {
lock.lock();
try {
while (isFull()) notFull.await();
boolean wasEmpty = isEmpty();
...
if (wasEmpty) notEmpty.signal();
} finally {
lock.unlock();
}
}
public E take() {
lock.lock();
try {
while (isEmpty()) notEmpty.await();
boolean wasFull = isFull();
...
if (wasFull) notFull.signal();
...
} finally {
lock.unlock();
}
}
...
}
Комментарии:
1. Спасибо, это выглядит хорошо. Я просто проверял LinkedBlockingQueue, и он использует ту же технику, но с двумя блокировками (put и take). Это повышение производительности или почему у них две блокировки?
2. @Franz: Да, они используют некоторую оптимизацию с двумя блокировками, как описано в комментариях.
ArrayBlockingQueue
однако использует одну блокировку точно так, как показано выше.
Ответ №2:
В общем, использование notifyAll()
— лучший подход по сравнению с notify()
. Как вы упомянули, вы могли бы использовать два объекта монитора, один для чтения и один для доступа на запись.
Использование notify()
вместо этого может привести к ошибкам, а снижение производительности при использовании notifyAll()
в большинстве случаев незначительно и обычно не требует другого кодирования, поскольку каждый ожидающий поток в любом случае должен быть подготовлен к ложным пробуждениям.