почему есть цикл while в put () LinkedBlockingQueue - PullRequest
0 голосов
/ 23 февраля 2019
public void put(E e) throws InterruptedException {
    if (e == null) throw new NullPointerException();
    int c = -1;
    Node<E> node = new Node<E>(e);
    final ReentrantLock putLock = this.putLock;
    final AtomicInteger count = this.count;
    putLock.lockInterruptibly();
    try {
        while (count.get() == capacity) {
            notFull.await();
        }
        enqueue(node);
        c = count.getAndIncrement();
        if (c + 1 < capacity)
            notFull.signal();
    } finally {
        putLock.unlock();
    }
    if (c == 0)
        signalNotEmpty();
}

почему существует цикл while?

Все потоки размещения закрываются с помощью putLock.

Ни один поток не может увеличить 'count', когда ожидающий поток удерживает putLock.

Ответы [ 3 ]

0 голосов
/ 28 февраля 2019

Существует фундаментальное свойство await (которое относится к внутренней блокировке через synchronized и также с использованием Object.wait), вы должны понимать:

Когда вы вызываете await,вы снимаете блокировку , с которой связана Condition.Обойти его невозможно, иначе никто не сможет получить блокировку, выполнить условие и вызвать для него signal.

Когда ваш ожидающий поток получает сигнал, он не получает блокировку.вернуться немедленно.Это было бы невозможно, поскольку поток, вызвавший signal, все еще владеет им.Вместо этого получатель будет пытаться повторно получить блокировку, мало чем отличаясь от вызова lockInterruptibly().

Но этот поток не обязательно является единственным потоком, пытающимся получить блокировку.Это даже не должно быть первым.Другой поток мог прибыть на put до сигнализации и ожидания блокировки на lockInterruptibly().Таким образом, даже если блокировка была корректной (что обычно не происходит), сигнальный поток не имеет приоритета.Даже если вы указали приоритет сигнальных потоков, может быть несколько потоков, о которых было сообщено по разным причинам.

Таким образом, другой поток, достигший put, может получить блокировку перед сигнальным потоком, найти место и сохранитьэлемент, даже не заботясь о сигналах.Затем, к тому времени, когда сигнализируемый поток получил блокировку, условие больше не выполняется.Таким образом, сигнализируемый поток никогда не может полагаться на действительность условия только потому, что он получил сигнал, и поэтому должен повторно проверить условие и снова вызвать await, если он не выполнен.

Это делает проверку условия вцикл стандартная идиома использования await, как описано в Condition интерфейс , а также Object.wait для случая использования встроенного монитора, только для полноты,Другими словами, это даже не относится к конкретному API.

Так как условие должно быть предварительно проверено и проверено в любом случае в цикле, спецификация даже допускает ложных пробуждений событие возврата потока из операции ожидания без фактического получения сигнала.Это может упростить реализацию блокировок на определенных платформах, не изменяя способ использования блокировки.

important Важно подчеркнуть, что при удержании нескольких блокировок только блокировка, связанная сусловие освобождено.

0 голосов
/ 13 марта 2019

@ Ответ Холдера правильный, но я хотел бы добавить более подробную информацию о следующем коде и вопросе.

putLock.lockInterruptibly();
try {
    while (count.get() == capacity) {
        notFull.await();
    }
    ...

Почему существует цикл while?PutLock блокирует весь поток размещения.

Цикл while является критической частью этого шаблона кода, который гарантирует, что при пробуждении потока от сигнала на notFull он гарантирует, чтодругой поток не попал туда первым и заново заполнил буфер.

Одна вещь, которую важно понять, что notFull определяется как условие для putLock:

private final Condition notFull = putLock.newCondition();

Когда поток вызывает notFull.await(), он разблокирует putLock, что означает, что несколько потоков могут работать notFull.await() одновременно.Поток будет пытаться восстановить блокировку только после вызова notFull.signal() (или signalAll()).

Состояние гонки происходит, если поток-A BLOCKED пытается получить putLock и поток-BWAITING на notFull.Если поток C удаляет что-то из очереди и сигнализирует notFull, поток B будет удален из очереди ожидания и помещен в заблокированную очередь на putLock, но, к сожалению, он будет позади thread-A, который уже был заблокирован.Поэтому, как только putLock разблокирован, поток-A получит putLock и поместит что-то в очередь, заполняя его снова.Когда поток B наконец получает putLock, он должен снова протестировать, чтобы увидеть, есть ли еще свободное пространство перед тем, как поставить (и переполнить) очередь.Вот почему while необходим.

Вторичной причиной цикла while, как также упоминал @Holder, является защита от ложных пробуждений, которые могут происходить при определенных архитектурах потоков, когда условие искусственно сигнализируется.Например, в некоторых архитектурах сигнал о любых условных сигналах всех условиях из-за ограничений ОС.

0 голосов
/ 23 февраля 2019

Функция Loop (*) блокирует поток, который называется методом put, когда емкость LinkedBlockingQueue заполнена.Когда другой метод вызова (или опроса) вызывает поток, в очереди будет место для нового элемента, а метод take будет сообщать о состоянии notFull, и ожидающий поток будет пробужден и сможет поместить элемент в очередь.

(*) Условие цикла должно гарантировать, что ложного пробуждения не произошло.

https://en.wikipedia.org/wiki/Spurious_wakeup

...