Search code examples
javaconcurrencyjava.util.concurrentblockingqueue

why is there a while loop in put() of LinkedBlockingQueue


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();
}

why is there a while loop?

All the putting thread is shut out by putLock.

No thread can increase 'count' when the waiting thread is holding the putLock.


Solution

  • There is a fundamental property of await (which applies to the intrinsic locking via synchronized and using Object.wait as well), you have to understand:

    When you invoke await, you are releasing the lock this Condition is associated with¹. There is no way around it, as otherwise, no-one could acquire the lock, make the condition fulfilled, and invoke signal on it.

    When your waiting thread gets signaled, it does not get the lock back immediately. That would not be possible, as the thread which invoked signal still owns it. Instead, the receiver will try to re-acquire the lock, not much different to calling lockInterruptibly().

    But this thread is not necessarily the only thread trying to acquire the lock. It doesn’t even have to be the first one. Another thread could have arrived at put before the signalling and waiting for the lock at lockInterruptibly(). So even if the lock was fair (which locks usually are not), the signaled thread had no precedence. Even if you gave signaled threads precedence, there could be multiple threads being signaled for different reasons.

    So another thread arriving at put could get the lock before the signaled thread, find that there is space, and store the element without ever bothering with signals. Then, by the time the signaled thread acquired the lock, the condition is not fulfilled anymore. So a signaled thread can never rely on the validity of the condition just because it received a signal and therefore has to re-check the condition and invoke await again if not fulfilled.

    This makes checking the condition in a loop the standard idiom of using await, as documented in the Condition interface, as well as Object.wait for the case of using the intrinsic monitor, just for completeness. In other words, this is not even specific to a particular API.

    Since the condition has to be pre-checked and re-checked in a loop anyway, the specification even allows for spurious wakeups, the event of a thread returning from the wait operation without actually receiving a signal. This may simplify lock implementations of certain platforms, while not changing the way a lock has to be used.

    ¹ It’s important to emphasize that when holding multiple locks, only the lock associated with the condition is released.