Дизайн блокировки очереди с несколькими мониторами

#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() в большинстве случаев незначительно и обычно не требует другого кодирования, поскольку каждый ожидающий поток в любом случае должен быть подготовлен к ложным пробуждениям.